liyabo 3 years ago
parent
commit
01f3484003
100 changed files with 25272 additions and 14 deletions
  1. 6 12
      .gitignore
  2. 191 0
      LICENSE
  3. 111 2
      README.md
  4. 89 0
      eladmin-activity/pom.xml
  5. 16 0
      eladmin-activity/src/main/java/me/zhengjie/modules/activiti/config/WebAppConfigurer.java
  6. 16 0
      eladmin-activity/src/main/java/me/zhengjie/modules/activiti/config/WebMvcConfig.java
  7. 44 0
      eladmin-activity/src/main/java/me/zhengjie/modules/activiti/main/StencilsetRestResource.java
  8. 289 0
      eladmin-activity/src/main/java/me/zhengjie/modules/activiti/rest/ActivitiController.java
  9. 19 0
      eladmin-activity/src/main/resources/config/application-act.yml
  10. BIN
      eladmin-activity/src/main/resources/favicon.ico
  11. 46 0
      eladmin-activity/src/main/resources/process/test.bpmn
  12. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/bg.png
  13. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/breadcrumbs.png
  14. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/checker-bg.png
  15. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/blue/message_catch.png
  16. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/business_rule.png
  17. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/error_catch.png
  18. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/error_throw.png
  19. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/manual.png
  20. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/message_catch.png
  21. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/message_throw.png
  22. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/receive.png
  23. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/script.png
  24. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/send.png
  25. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/service.png
  26. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/signal_catch.png
  27. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/signal_throw.png
  28. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/timer.png
  29. BIN
      eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/user.png
  30. 130 0
      eladmin-activity/src/main/resources/static/diagram-viewer/index.html
  31. 74 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/ActivitiRest.js
  32. 0 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/ActivityImpl.js
  33. 603 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/Color.js
  34. 270 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/LineBreakMeasurer.js
  35. 387 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/Polyline.js
  36. 2172 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/ProcessDiagramCanvas.js
  37. 1087 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/ProcessDiagramGenerator.js
  38. 125 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/jquery/jquery.asyncqueue.js
  39. 9266 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/jquery/jquery.js
  40. 131 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/jquery/jquery.progressbar.js
  41. 23 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/jstools.js
  42. 9 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/raphael.2.1.1.js
  43. 9 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/raphael.js
  44. 5815 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/raphael_uncompressed.js
  45. 441 0
      eladmin-activity/src/main/resources/static/diagram-viewer/js/textlayout.js
  46. 0 0
      eladmin-activity/src/main/resources/static/diagram-viewer/style.css
  47. 26 0
      eladmin-activity/src/main/resources/static/editor-app/app-cfg.js
  48. 433 0
      eladmin-activity/src/main/resources/static/editor-app/app.js
  49. 150 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-assignment-controller.js
  50. 58 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-condition-expression-controller.js
  51. 18 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-custom-controllers.js
  52. 115 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-default-controllers.js
  53. 266 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-event-listeners-controller.js
  54. 326 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-execution-listeners-controller.js
  55. 192 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-fields-controller.js
  56. 276 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-form-properties-controller.js
  57. 158 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-in-parameters-controller.js
  58. 137 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-message-definitions-controller.js
  59. 48 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-message-scope-controller.js
  60. 34 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-multiinstance-controller.js
  61. 158 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-out-parameters-controller.js
  62. 130 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-sequenceflow-order-controller.js
  63. 136 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-signal-definitions-controller.js
  64. 47 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-signal-scope-controller.js
  65. 325 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties-task-listeners-controller.js
  66. 99 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties.js
  67. 4 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/assignment-display-template.html
  68. 44 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/assignment-popup.html
  69. 4 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/assignment-write-template.html
  70. 4 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/boolean-property-template.html
  71. 2 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/condition-expression-display-template.html
  72. 29 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/condition-expression-popup.html
  73. 4 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/condition-expression-write-template.html
  74. 4 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/default-value-display-template.html
  75. 3 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/event-listeners-display-template.html
  76. 115 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/event-listeners-popup.html
  77. 4 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/event-listeners-write-template.html
  78. 3 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/execution-listeners-display-template.html
  79. 101 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/execution-listeners-popup.html
  80. 4 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/execution-listeners-write-template.html
  81. 17 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/feedback-popup.html
  82. 3 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/fields-display-template.html
  83. 61 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/fields-popup.html
  84. 4 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/fields-write-template.html
  85. 3 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/form-properties-display-template.html
  86. 117 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/form-properties-popup.html
  87. 4 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/form-properties-write-template.html
  88. 3 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/in-parameters-display-template.html
  89. 53 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/in-parameters-popup.html
  90. 4 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/in-parameters-write-template.html
  91. 2 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/message-definitions-display-template.html
  92. 50 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/message-definitions-popup.html
  93. 3 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/message-definitions-write-template.html
  94. 4 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/message-property-write-template.html
  95. 8 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/multiinstance-property-write-template.html
  96. 3 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/out-parameters-display-template.html
  97. 53 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/out-parameters-popup.html
  98. 4 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/out-parameters-write-template.html
  99. 3 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/sequenceflow-order-display-template.html
  100. 47 0
      eladmin-activity/src/main/resources/static/editor-app/configuration/properties/sequenceflow-order-popup.html

+ 6 - 12
.gitignore

@@ -1,12 +1,6 @@
-*.class
-
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
-
-# Package Files #
-*.jar
-*.war
-*.ear
-
-# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
-hs_err_pid*
+### IDEA ###
+.idea/*
+*.iml
+*/target/*
+*/*.iml
+/.gradle/

+ 191 - 0
LICENSE

@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "{}" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright 2019-2020 Zheng Jie
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 111 - 2
README.md

@@ -1,4 +1,113 @@
-## iot-hjjc
+<h1 style="text-align: center">EL-ADMIN 后台管理系统</h1>
+<div style="text-align: center">
 
-永天-环境监测
+[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/elunez/eladmin/blob/master/LICENSE)
+[![star](https://gitee.com/elunez/eladmin/badge/star.svg?theme=white)](https://gitee.com/elunez/eladmin)
+[![GitHub stars](https://img.shields.io/github/stars/elunez/eladmin.svg?style=social&label=Stars)](https://github.com/elunez/eladmin)
+[![GitHub forks](https://img.shields.io/github/forks/elunez/eladmin.svg?style=social&label=Fork)](https://github.com/elunez/eladmin)
 
+</div>
+
+#### 项目简介
+一个基于 Spring Boot 2.1.0 、 Spring Boot Jpa、 JWT、Spring Security、Redis、Vue的前后端分离的后台管理系统
+
+**开发文档:**  [https://el-admin.vip](https://el-admin.vip)
+
+**体验地址:**  [https://el-admin.xin](https://el-admin.xin)
+
+**账号密码:** `admin / 123456`
+
+#### 活动福利
+
+- 七牛云 2021 ECUG Con 全球技术大会内部票:给大家一共搞来五张小一千块钱的内部票。想要的加群【947578238】联系群主
+- 七牛云 2021 ECUG Con 全球技术大会早鸟特惠票:[点我抢购](http://www.huodongxing.com/event/1573487912300?coupon=EL666)
+
+#### 项目源码
+
+|     |   后端源码  |   前端源码  |
+|---  |--- | --- |
+|  github   |  https://github.com/elunez/eladmin   |  https://github.com/elunez/eladmin-web   |
+|  码云   |  https://gitee.com/elunez/eladmin   |  https://gitee.com/elunez/eladmin-web   |
+
+#### 主要特性
+- 使用最新技术栈,社区资源丰富。
+- 高效率开发,代码生成器可一键生成前后端代码
+- 支持数据字典,可方便地对一些状态进行管理
+- 支持接口限流,避免恶意请求导致服务层压力过大
+- 支持接口级别的功能权限与数据权限,可自定义操作
+- 自定义权限注解与匿名接口注解,可快速对接口拦截与放行
+- 对一些常用地前端组件封装:表格数据请求、数据字典等
+- 前后端统一异常拦截处理,统一输出异常,避免繁琐的判断
+- 支持在线用户管理与服务器性能监控,支持限制单用户登录
+- 支持运维管理,可方便地对远程服务器的应用进行部署与管理
+
+####  系统功能
+- 用户管理:提供用户的相关配置,新增用户后,默认密码为123456
+- 角色管理:对权限与菜单进行分配,可根据部门设置角色的数据权限
+- 菜单管理:已实现菜单动态路由,后端可配置化,支持多级菜单
+- 部门管理:可配置系统组织架构,树形表格展示
+- 岗位管理:配置各个部门的职位
+- 字典管理:可维护常用一些固定的数据,如:状态,性别等
+- 系统日志:记录用户操作日志与异常日志,方便开发人员定位排错
+- SQL监控:采用druid 监控数据库访问性能,默认用户名admin,密码123456
+- 定时任务:整合Quartz做定时任务,加入任务日志,任务运行情况一目了然
+- 代码生成:高灵活度生成前后端代码,减少大量重复的工作任务
+- 邮件工具:配合富文本,发送html格式的邮件
+- 七牛云存储:可同步七牛云存储的数据到系统,无需登录七牛云直接操作云数据
+- 支付宝支付:整合了支付宝支付并且提供了测试账号,可自行测试
+- 服务监控:监控服务器的负载情况
+- 运维管理:一键部署你的应用
+
+#### 项目结构
+项目采用按功能分模块的开发方式,结构如下
+
+- `eladmin-common` 为系统的公共模块,各种工具类,公共配置存在该模块
+
+- `eladmin-system` 为系统核心模块也是项目入口模块,也是最终需要打包部署的模块
+
+- `eladmin-logging` 为系统的日志模块,其他模块如果需要记录日志需要引入该模块
+
+- `eladmin-tools` 为第三方工具模块,包含:图床、邮件、云存储、本地存储、支付宝
+
+- `eladmin-generator` 为系统的代码生成模块,代码生成的模板在 system 模块中
+
+#### 详细结构
+
+```
+- eladmin-common 公共模块
+    - annotation 为系统自定义注解
+    - aspect 自定义注解的切面
+    - base 提供了Entity、DTO基类和mapstruct的通用mapper
+    - config 自定义权限实现、redis配置、swagger配置、Rsa配置等
+    - exception 项目统一异常的处理
+    - utils 系统通用工具类
+- eladmin-system 系统核心模块(系统启动入口)
+	- config 配置跨域与静态资源,与数据权限
+	    - thread 线程池相关
+	- modules 系统相关模块(登录授权、系统监控、定时任务、运维管理等)
+- eladmin-logging 系统日志模块
+- eladmin-tools 系统第三方工具模块
+- eladmin-generator 系统代码生成模块
+```
+
+#### 特别鸣谢
+
+- 感谢 [JetBrains](https://www.jetbrains.com/) 提供的非商业开源软件开发授权
+
+- 感谢 [七牛云](https://www.qiniu.com/) 提供的免费云存储与CDN加速支持
+
+- 感谢 [PanJiaChen](https://github.com/PanJiaChen/vue-element-admin) 大佬提供的前端模板
+
+- 感谢 [Moxun](https://github.com/moxun1639) 大佬提供的前端 Curd 通用组件
+
+- 感谢 [zhy6599](https://gitee.com/zhy6599) 大佬提供的后端运维管理相关功能
+
+- 感谢 [j.yao.SUSE](https://github.com/everhopingandwaiting) 大佬提供的匿名接口与Redis限流等功能
+
+- 感谢 [d15801543974](https://github.com/d15801543974) 大佬提供的基于注解的通用查询方式
+
+#### 项目捐赠
+项目的发展离不开你的支持,请作者喝杯咖啡吧☕  [Donate](https://el-admin.vip/donation/)
+
+#### 反馈交流
+- QQ交流群:一群:<strike>891137268</strike> 已满、二群:947578238

+ 89 - 0
eladmin-activity/pom.xml

@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>eladmin</artifactId>
+        <groupId>me.zhengjie</groupId>
+        <version>2.6</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>eladmin-activity</artifactId>
+    <name>activiti工作流模块</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>me.zhengjie</groupId>
+            <artifactId>eladmin-common</artifactId>
+            <version>2.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-spring-boot-starter-basic</artifactId>
+            <version>6.0.0</version>
+        </dependency>
+
+        <!-- Activiti modeler 可视化依赖 -->
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-modeler</artifactId>
+            <version>5.23.0</version>
+        </dependency>
+
+        <!-- Activity6 集成 Modeler 需要 jar 包 -->
+        <dependency>
+            <groupId>org.apache.xmlgraphics</groupId>
+            <artifactId>batik-transcoder</artifactId>
+            <version>1.8</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.xmlgraphics</groupId>
+            <artifactId>batik-codec</artifactId>
+            <version>1.8</version>
+        </dependency>
+        <dependency>
+            <groupId>org.activiti</groupId>
+            <artifactId>activiti-json-converter</artifactId>
+            <version>6.0.0</version>
+        </dependency>
+
+    </dependencies>
+
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <artifactId>maven-resources-plugin</artifactId>
+                    <configuration>
+                        <encoding>utf-8</encoding>
+                        <useDefaultDelimiters>true</useDefaultDelimiters>
+                        <nonFilteredFileExtensions>
+                            <nonFilteredFileExtension>woff</nonFilteredFileExtension>
+                            <nonFilteredFileExtension>woff2</nonFilteredFileExtension>
+                            <nonFilteredFileExtension>eot</nonFilteredFileExtension>
+                            <nonFilteredFileExtension>ttf</nonFilteredFileExtension>
+                            <nonFilteredFileExtension>svg</nonFilteredFileExtension>
+                        </nonFilteredFileExtensions>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <resources>
+            <!--<resource>-->
+                <!--<directory>src/main/resources</directory>-->
+                <!--<includes>-->
+                    <!--<include>**.*</include>-->
+                    <!--<include>**/**.*</include>-->
+                <!--</includes>-->
+                <!--<filtering>true</filtering>-->
+            <!--</resource>-->
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>false</filtering>
+            </resource>
+        </resources>
+    </build>
+</project>

+ 16 - 0
eladmin-activity/src/main/java/me/zhengjie/modules/activiti/config/WebAppConfigurer.java

@@ -0,0 +1,16 @@
+package me.zhengjie.modules.activiti.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.filter.HttpPutFormContentFilter;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebAppConfigurer implements WebMvcConfigurer {
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
+    }
+
+}

+ 16 - 0
eladmin-activity/src/main/java/me/zhengjie/modules/activiti/config/WebMvcConfig.java

@@ -0,0 +1,16 @@
+package me.zhengjie.modules.activiti.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.filter.HttpPutFormContentFilter;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+
+@Configuration
+@EnableWebMvc
+public class WebMvcConfig extends WebMvcConfigurerAdapter {
+    @Bean
+    public HttpPutFormContentFilter httpPutFormContentFilter() {
+        return new HttpPutFormContentFilter();
+    }
+}

+ 44 - 0
eladmin-activity/src/main/java/me/zhengjie/modules/activiti/main/StencilsetRestResource.java

@@ -0,0 +1,44 @@
+/* Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package me.zhengjie.modules.activiti.main;
+
+import java.io.InputStream;
+
+import com.alibaba.fastjson.JSONObject;
+import me.zhengjie.annotation.AnonymousAccess;
+import org.activiti.engine.ActivitiException;
+import org.apache.commons.io.IOUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author Tijs Rademakers
+ */
+@RestController
+@RequestMapping("/api/activiti")
+public class StencilsetRestResource {
+  
+  @RequestMapping(value="/editor/stencilset", method = RequestMethod.GET, produces = "application/json;charset=utf-8")
+  @AnonymousAccess
+  public @ResponseBody Object getStencilset() {
+    InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("stencilset.json");
+    try {
+//      return IOUtils.toString(stencilsetStream, "utf-8");
+      return JSONObject.parseObject(IOUtils.toString(stencilsetStream, "utf-8"));
+    } catch (Exception e) {
+      throw new ActivitiException("Error while loading stencil set", e);
+    }
+  }
+}

+ 289 - 0
eladmin-activity/src/main/java/me/zhengjie/modules/activiti/rest/ActivitiController.java

@@ -0,0 +1,289 @@
+package me.zhengjie.modules.activiti.rest;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import me.zhengjie.annotation.AnonymousAccess;
+import me.zhengjie.exception.BadRequestException;
+import me.zhengjie.utils.PageUtil;
+import org.activiti.bpmn.converter.BpmnXMLConverter;
+import org.activiti.bpmn.model.BpmnModel;
+import org.activiti.editor.constants.ModelDataJsonConstants;
+import org.activiti.editor.language.json.converter.BpmnJsonConverter;
+import org.activiti.engine.ActivitiException;
+import org.activiti.engine.ProcessEngine;
+import org.activiti.engine.ProcessEngines;
+import org.activiti.engine.RepositoryService;
+import org.activiti.engine.impl.persistence.entity.ModelEntityImpl;
+import org.activiti.engine.repository.Deployment;
+import org.activiti.engine.repository.Model;
+import org.activiti.engine.repository.ModelQuery;
+import org.apache.batik.transcoder.TranscoderInput;
+import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.image.PNGTranscoder;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Controller;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+@Controller
+@RequiredArgsConstructor
+@Api(tags = "工作流")
+@RequestMapping("/api/activiti")
+public class ActivitiController implements ModelDataJsonConstants {
+    protected static final Logger LOGGER = LoggerFactory.getLogger(ActivitiController.class);
+
+    @Autowired
+    private RepositoryService repositoryService;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    @ApiOperation("查询模型")
+    @GetMapping("/model/list")
+    @AnonymousAccess
+    public ResponseEntity<Object> query(ModelEntityImpl model, int page, int size) {
+        ModelQuery modelQuery = repositoryService.createModelQuery();
+        if (StringUtils.isNotBlank(model.getName())) {
+            modelQuery.modelNameLike("%" + model.getName() + "%");
+        }
+        modelQuery.orderByLastUpdateTime().desc();
+        List<Model> models = modelQuery.list();
+        return new ResponseEntity<>(PageUtil.toPage(PageUtil.toPage(page, size, models), models.size()), HttpStatus.OK);
+    }
+
+    @ApiOperation("创建基本模型,跳转设计页面")
+    @GetMapping("/model/create")
+    @AnonymousAccess
+    public void createModel(HttpServletRequest request, HttpServletResponse response) {
+        try {
+            String modelName = "modelName";
+            String modelKey = "modelKey";
+            String description = "description";
+
+            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
+
+            RepositoryService repositoryService = processEngine.getRepositoryService();
+
+            ObjectMapper objectMapper = new ObjectMapper();
+            ObjectNode editorNode = objectMapper.createObjectNode();
+            editorNode.put("id", "canvas");
+            editorNode.put("resourceId", "canvas");
+            ObjectNode stencilSetNode = objectMapper.createObjectNode();
+            stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
+            editorNode.put("stencilset", stencilSetNode);
+            // 定义新模型
+            Model modelData = repositoryService.newModel();
+//            Model modelData = repositoryService.getModel("40002");
+
+            ObjectNode modelObjectNode = objectMapper.createObjectNode();
+            modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, modelName);
+            modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
+            modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
+            modelData.setMetaInfo(modelObjectNode.toString());
+            modelData.setName(modelName);
+            modelData.setKey(modelKey);
+
+            //保存模型
+            repositoryService.saveModel(modelData);
+            repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
+            System.out.println("request.getContextPath() ==>> " + request.getContextPath());
+            response.sendRedirect("/static/modeler.html?modelId=" + modelData.getId());
+        } catch (Exception e) {
+        }
+    }
+
+    @ApiOperation("创建基本模型,返回一个modelID")
+    @GetMapping("/model/createModel")
+    @AnonymousAccess
+    public ResponseEntity<Object> createModelAPI(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        String modelName = "modelName";
+        String modelKey = "modelKey";
+        String description = "description";
+
+        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
+
+        RepositoryService repositoryService = processEngine.getRepositoryService();
+
+        ObjectMapper objectMapper = new ObjectMapper();
+        ObjectNode editorNode = objectMapper.createObjectNode();
+        editorNode.put("id", "canvas");
+        editorNode.put("resourceId", "canvas");
+        ObjectNode stencilSetNode = objectMapper.createObjectNode();
+        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
+        editorNode.put("stencilset", stencilSetNode);
+        // 定义新模型
+        Model modelData = repositoryService.newModel();
+
+        ObjectNode modelObjectNode = objectMapper.createObjectNode();
+        modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, modelName);
+        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
+        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
+        modelData.setMetaInfo(modelObjectNode.toString());
+        modelData.setName(modelName);
+        modelData.setKey(modelKey);
+
+        //保存模型
+        repositoryService.saveModel(modelData);
+        repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
+        return new ResponseEntity<>(modelData.getId(), HttpStatus.OK);
+    }
+
+    @ApiOperation("创建基本模型")
+    @GetMapping("/model/{modelId}/update")
+    @AnonymousAccess
+    public void createModel(@PathVariable String modelId, HttpServletRequest request, HttpServletResponse response) {
+        try {
+            response.sendRedirect("/static/modeler.html?modelId=" + modelId);
+        } catch (Exception e) {
+        }
+    }
+
+    @ApiOperation("获取流程定义json数据")
+    @RequestMapping(value = "/model/{modelId}/json", method = RequestMethod.GET, produces = "application/json")
+    @ResponseBody
+    @AnonymousAccess
+    public Object getEditorJson(@PathVariable String modelId) {
+        ObjectNode modelNode = null;
+
+        Model model = repositoryService.getModel(modelId);
+
+        if (model != null) {
+            try {
+                if (StringUtils.isNotEmpty(model.getMetaInfo())) {
+                    modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
+                } else {
+                    modelNode = objectMapper.createObjectNode();
+                    modelNode.put(MODEL_NAME, model.getName());
+                }
+                modelNode.put(MODEL_ID, model.getId());
+                ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(
+                        new String(repositoryService.getModelEditorSource(model.getId()), "utf-8"));
+                modelNode.putPOJO("model", editorJsonNode);
+
+            } catch (Exception e) {
+                LOGGER.error("Error creating model JSON", e);
+                throw new ActivitiException("Error creating model JSON", e);
+            }
+        }
+        return JSONObject.parseObject(modelNode.toString());
+    }
+
+    @ApiOperation("保存流程定义数据")
+    @RequestMapping(value = "/model/{modelId}/save", method = RequestMethod.PUT)
+    @ResponseStatus(value = HttpStatus.OK)
+    @AnonymousAccess
+    public void saveModel(@PathVariable String modelId, @RequestParam("name") String name,
+                          @RequestParam("json_xml") String json_xml, @RequestParam("svg_xml") String svg_xml,
+                          @RequestParam("description") String description, HttpServletRequest request) {//对接收参数进行了修改
+        try {
+
+            Model model = repositoryService.getModel(modelId);
+
+            ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
+            modelJson.put(MODEL_NAME, name);
+            modelJson.put(MODEL_DESCRIPTION, description);
+            model.setMetaInfo(modelJson.toString());
+            model.setName(name);
+
+            repositoryService.saveModel(model);
+
+            repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes("utf-8"));
+
+            InputStream svgStream = new ByteArrayInputStream(svg_xml.getBytes("utf-8"));
+            TranscoderInput input = new TranscoderInput(svgStream);
+
+            PNGTranscoder transcoder = new PNGTranscoder();
+            // Setup output
+            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+            TranscoderOutput output = new TranscoderOutput(outStream);
+
+            // Do the transformation
+            transcoder.transcode(input, output);
+            final byte[] result = outStream.toByteArray();
+            repositoryService.addModelEditorSourceExtra(model.getId(), result);
+            outStream.close();
+
+        } catch (Exception e) {
+            LOGGER.error("Error saving model", e);
+            throw new ActivitiException("Error saving model", e);
+        }
+    }
+
+    @ApiOperation("根据ID查询模型")
+    @GetMapping("/model/{modelId}")
+    @AnonymousAccess
+    public ResponseEntity<Object> getById(@PathVariable("modelId") String modelId) {
+        Model model = repositoryService.createModelQuery().modelId(modelId).singleResult();
+        return new ResponseEntity<>(model, HttpStatus.OK);
+    }
+
+    @ApiOperation("删除模型")
+    @DeleteMapping("/model/{modelId}")
+    @AnonymousAccess
+    public ResponseEntity<Object> delete(@PathVariable("modelId") String modelId) {
+        repositoryService.deleteModel(modelId);
+        return new ResponseEntity<>(modelId, HttpStatus.OK);
+    }
+
+    @ApiOperation("部署模型")
+    @GetMapping("/model/{modelId}/deploy")
+    @AnonymousAccess
+    public ResponseEntity<Object> deploy(@PathVariable("modelId") String modelId) throws Exception {
+        // 获取模型
+        Model modelData = repositoryService.getModel(modelId);
+
+        if (modelData == null) {
+            throw new BadRequestException("模型不存在");
+        }
+
+        byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
+
+        if (bytes == null) {
+            throw new BadRequestException("请先设计流程定义并成功保存,再进行部署");
+        }
+
+        JsonNode modelNode = null;
+        modelNode = new ObjectMapper().readTree(bytes);
+        BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
+        if (model.getProcesses().size() == 0) {
+            throw new BadRequestException("流程定义不符要求,请至少设计一条主线流程");
+        }
+        byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
+        //发布流程
+        String processName = modelData.getName() + ".bpmn20.xml";
+        Deployment deployment = repositoryService.createDeployment()
+                .name(modelData.getName())
+                .key(modelData.getKey())
+                .category(modelData.getCategory())
+                .addString(processName, new String(bpmnBytes, StandardCharsets.UTF_8))
+                .deploy();
+        modelData.setDeploymentId(deployment.getId());
+        repositoryService.saveModel(modelData);
+
+        return new ResponseEntity<>("部署成功", HttpStatus.OK);
+    }
+
+
+}

+ 19 - 0
eladmin-activity/src/main/resources/config/application-act.yml

@@ -0,0 +1,19 @@
+spring:
+  activiti:
+    #设置成true以后,会在项目启动的时候自动创建Activiti表结构,首次数据表建好后建议改成false
+    database-schema-update: false
+    #是否自动部署流程文件(当指定文件夹下无资源文件/不需要部署的时候设置为false)
+    check-process-definitions: false
+    #保存历史数据得级别,分为none、activity、audit(默认)、full
+    history-level: full
+    #修改默认的流程文件存储位置
+    process-definition-location-prefix: classpath:/process/
+
+  resources:
+    static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
+
+  mvc:
+    view:
+      prefix: /
+      suffix: .html
+    static-path-pattern: /static/**

BIN
eladmin-activity/src/main/resources/favicon.ico


+ 46 - 0
eladmin-activity/src/main/resources/process/test.bpmn

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1614586665308" name="" targetNamespace="http://www.activiti.org/testm1614586665308" typeLanguage="http://www.w3.org/2001/XMLSchema">
+  <process id="myProcess_1" isClosed="false" isExecutable="true" processType="None">
+    <startEvent id="_2" name="StartEvent"/>
+    <userTask activiti:exclusive="true" id="_3" name="UserTask"/>
+    <sequenceFlow id="_4" sourceRef="_2" targetRef="_3"/>
+    <endEvent id="_5" name="EndEvent"/>
+    <sequenceFlow id="_6" sourceRef="_3" targetRef="_5"/>
+  </process>
+  <bpmndi:BPMNDiagram documentation="background=#FFFFFF;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
+    <bpmndi:BPMNPlane bpmnElement="myProcess_1">
+      <bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
+        <dc:Bounds height="32.0" width="32.0" x="175.0" y="35.0"/>
+        <bpmndi:BPMNLabel>
+          <dc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
+        </bpmndi:BPMNLabel>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
+        <dc:Bounds height="55.0" width="85.0" x="150.0" y="160.0"/>
+        <bpmndi:BPMNLabel>
+          <dc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
+        </bpmndi:BPMNLabel>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
+        <dc:Bounds height="32.0" width="32.0" x="170.0" y="310.0"/>
+        <bpmndi:BPMNLabel>
+          <dc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
+        </bpmndi:BPMNLabel>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="_4" id="BPMNEdge__4" sourceElement="_2" targetElement="_3">
+        <di:waypoint x="191.0" y="67.0"/>
+        <di:waypoint x="191.0" y="160.0"/>
+        <bpmndi:BPMNLabel>
+          <dc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
+        </bpmndi:BPMNLabel>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="_3" targetElement="_5">
+        <di:waypoint x="186.0" y="215.0"/>
+        <di:waypoint x="186.0" y="310.0"/>
+        <bpmndi:BPMNLabel>
+          <dc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
+        </bpmndi:BPMNLabel>
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</definitions>

BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/bg.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/breadcrumbs.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/checker-bg.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/blue/message_catch.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/business_rule.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/error_catch.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/error_throw.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/manual.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/message_catch.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/message_throw.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/receive.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/script.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/send.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/service.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/signal_catch.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/signal_throw.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/timer.png


BIN
eladmin-activity/src/main/resources/static/diagram-viewer/images/deployer/user.png


+ 130 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/index.html

@@ -0,0 +1,130 @@
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  
+  <link rel="stylesheet" href="style.css" type="text/css" media="screen">
+  <script src="js/jstools.js" type="text/javascript" charset="utf-8"></script>
+  <script src="js/raphael.js" type="text/javascript" charset="utf-8"></script>
+  
+  <script src="js/jquery/jquery.js" type="text/javascript" charset="utf-8"></script>
+  <script src="js/jquery/jquery.progressbar.js" type="text/javascript" charset="utf-8"></script>
+  <script src="js/jquery/jquery.asyncqueue.js" type="text/javascript" charset="utf-8"></script>
+  
+  <script src="js/Color.js" type="text/javascript" charset="utf-8"></script>
+  <script src="js/Polyline.js" type="text/javascript" charset="utf-8"></script>
+  <script src="js/ActivityImpl.js" type="text/javascript" charset="utf-8"></script>
+  <script src="js/ActivitiRest.js" type="text/javascript" charset="utf-8"></script>
+  <script src="js/LineBreakMeasurer.js" type="text/javascript" charset="utf-8"></script>
+  <script src="js/ProcessDiagramGenerator.js" type="text/javascript" charset="utf-8"></script>
+  <script src="js/ProcessDiagramCanvas.js" type="text/javascript" charset="utf-8"></script>
+  
+  <style type="text/css" media="screen">
+    
+  </style>
+</head>
+<body>
+<div class="wrapper">
+  <div id="pb1"></div>
+  <div id="overlayBox" >
+    <div id="diagramBreadCrumbs" class="diagramBreadCrumbs" onmousedown="return false" onselectstart="return false"></div>
+    <div id="diagramHolder" class="diagramHolder"></div>
+    <div class="diagram-info" id="diagramInfo"></div>
+  </div>
+</div>
+<script language='javascript'>
+var DiagramGenerator = {};
+var pb1;
+$(document).ready(function(){
+  var query_string = {};
+  var query = window.location.search.substring(1);
+  var vars = query.split("&");
+  for (var i=0;i<vars.length;i++) {
+    var pair = vars[i].split("=");
+    query_string[pair[0]] = pair[1];
+  } 
+  
+  var processDefinitionId = query_string["processDefinitionId"];
+  var processInstanceId = query_string["processInstanceId"];
+  
+  console.log("Initialize progress bar");
+  
+  pb1 = new $.ProgressBar({
+    boundingBox: '#pb1',
+    label: 'Progressbar!',
+    on: {
+      complete: function() {
+        console.log("Progress Bar COMPLETE");
+        this.set('label', 'complete!');
+        if (processInstanceId) {
+          ProcessDiagramGenerator.drawHighLights(processInstanceId);
+        }
+      },
+      valueChange: function(e) {
+        this.set('label', e.newVal + '%');
+      }
+    },
+    value: 0
+  });
+  console.log("Progress bar inited");
+  
+  ProcessDiagramGenerator.options = {
+    diagramBreadCrumbsId: "diagramBreadCrumbs",
+    diagramHolderId: "diagramHolder",
+    diagramInfoId: "diagramInfo",
+    on: {
+      click: function(canvas, element, contextObject){
+        var mouseEvent = this;
+        console.log("[CLICK] mouseEvent: %o, canvas: %o, clicked element: %o, contextObject: %o", mouseEvent, canvas, element, contextObject);
+
+        if (contextObject.getProperty("type") == "callActivity") {
+          var processDefinitonKey = contextObject.getProperty("processDefinitonKey");
+          var processDefinitons = contextObject.getProperty("processDefinitons");
+          var processDefiniton = processDefinitons[0];
+          console.log("Load callActivity '" + processDefiniton.processDefinitionKey + "', contextObject: ", contextObject);
+
+          // Load processDefinition
+        ProcessDiagramGenerator.drawDiagram(processDefiniton.processDefinitionId);
+        }
+      },
+      rightClick: function(canvas, element, contextObject){
+        var mouseEvent = this;
+        console.log("[RIGHTCLICK] mouseEvent: %o, canvas: %o, clicked element: %o, contextObject: %o", mouseEvent, canvas, element, contextObject);
+      },
+      over: function(canvas, element, contextObject){
+        var mouseEvent = this;
+        //console.log("[OVER] mouseEvent: %o, canvas: %o, clicked element: %o, contextObject: %o", mouseEvent, canvas, element, contextObject);
+
+        // TODO: show tooltip-window with contextObject info
+        ProcessDiagramGenerator.showActivityInfo(contextObject);
+      },
+      out: function(canvas, element, contextObject){
+        var mouseEvent = this;
+        //console.log("[OUT] mouseEvent: %o, canvas: %o, clicked element: %o, contextObject: %o", mouseEvent, canvas, element, contextObject);
+
+        ProcessDiagramGenerator.hideInfo();
+      }
+    }
+  };
+  
+  var baseUrl = window.document.location.protocol + "//" + window.document.location.host + "/";
+  var shortenedUrl = window.document.location.href.replace(baseUrl, "");
+  baseUrl = baseUrl + shortenedUrl.substring(0, shortenedUrl.indexOf("/"));
+  
+  ActivitiRest.options = {
+    processInstanceHighLightsUrl: baseUrl + "/service/process-instance/{processInstanceId}/highlights?callback=?",
+    processDefinitionUrl: baseUrl + "/service/process-definition/{processDefinitionId}/diagram-layout?callback=?",
+    processDefinitionByKeyUrl: baseUrl + "/service/process-definition/{processDefinitionKey}/diagram-layout?callback=?"
+  };
+  
+  if (processDefinitionId) {
+    ProcessDiagramGenerator.drawDiagram(processDefinitionId);
+    
+  } else {
+    alert("processDefinitionId parameter is required");
+  }
+});
+
+
+</script>
+</body>
+</html>

+ 74 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/ActivitiRest.js

@@ -0,0 +1,74 @@
+var ActivitiRest = {
+	options: {},
+	getProcessDefinitionByKey: function(processDefinitionKey, callback) {
+		var url = Lang.sub(this.options.processDefinitionByKeyUrl, {processDefinitionKey: processDefinitionKey});
+		
+		$.ajax({
+			url: url,
+			dataType: 'jsonp',
+			cache: false,
+			async: true,
+			success: function(data, textStatus) {
+				var processDefinition = data;
+				if (!processDefinition) {
+					console.error("Process definition '" + processDefinitionKey + "' not found");
+				} else {
+				  callback.apply({processDefinitionId: processDefinition.id});
+				}
+			}
+		}).done(function(data, textStatus) {
+			console.log("ajax done");
+		}).fail(function(jqXHR, textStatus, error){
+			console.error('Get diagram layout['+processDefinitionKey+'] failure: ', textStatus, 'error: ', error, jqXHR);
+		});
+	},
+	
+	getProcessDefinition: function(processDefinitionId, callback) {
+		var url = Lang.sub(this.options.processDefinitionUrl, {processDefinitionId: processDefinitionId});
+		
+		$.ajax({
+			url: url,
+			dataType: 'jsonp',
+			cache: false,
+			async: true,
+			success: function(data, textStatus) {
+				var processDefinitionDiagramLayout = data;
+				if (!processDefinitionDiagramLayout) {
+					console.error("Process definition diagram layout '" + processDefinitionId + "' not found");
+					return;
+				} else {
+					callback.apply({processDefinitionDiagramLayout: processDefinitionDiagramLayout});
+				}
+			}
+		}).done(function(data, textStatus) {
+			console.log("ajax done");
+		}).fail(function(jqXHR, textStatus, error){
+			console.log('Get diagram layout['+processDefinitionId+'] failure: ', textStatus, jqXHR);
+		});
+	},
+	
+	getHighLights: function(processInstanceId, callback) {
+		var url = Lang.sub(this.options.processInstanceHighLightsUrl, {processInstanceId: processInstanceId});
+		
+		$.ajax({
+			url: url,
+			dataType: 'jsonp',
+			cache: false,
+			async: true,
+			success: function(data, textStatus) {
+				console.log("ajax returned data");
+				var highLights = data;
+				if (!highLights) {
+					console.log("highLights not found");
+					return;
+				} else {
+					callback.apply({highLights: highLights});
+				}
+			}
+		}).done(function(data, textStatus) {
+			console.log("ajax done");
+		}).fail(function(jqXHR, textStatus, error){
+		  console.log('Get HighLights['+processInstanceId+'] failure: ', textStatus, jqXHR);
+		});
+	}
+};

File diff suppressed because it is too large
+ 0 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/ActivityImpl.js


+ 603 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/Color.js

@@ -0,0 +1,603 @@
+/**
+ * Web color table
+ * 
+ * @author Dmitry Farafonov
+ */
+
+var Color = {
+   /**
+   * The color white.  In the default sRGB space.
+   */
+  white     : Raphael.getRGB("rgb(255,255,255)"),
+  
+  /**
+   * The color white.  In the default sRGB space.
+   */
+  WHITE : this.white,
+  
+  /**
+   * The color light gray.  In the default sRGB space.
+   */
+  lightGray : Raphael.getRGB("rgb(192, 192, 192)"),
+  
+  /**
+   * The color light gray.  In the default sRGB space.
+   */
+  LIGHT_GRAY : this.lightGray,
+  
+  /**
+   * The color gray.  In the default sRGB space.
+   */
+  gray : Raphael.getRGB("rgb(128, 128, 128)"),
+  
+  /**
+   * The color gray.  In the default sRGB space.
+   */
+  GRAY : this.gray,
+  
+  /**
+   * The color dark gray.  In the default sRGB space.
+   */
+  darkGray : Raphael.getRGB("rgb(64, 64, 64)"),
+  
+  /**
+   * The color dark gray.  In the default sRGB space.
+   */
+  DARK_GRAY : this.darkGray,
+  
+  /**
+   * The color black.  In the default sRGB space.
+   */
+  black : Raphael.getRGB("rgb(0, 0, 0)"),
+  
+  /**
+   * The color black.  In the default sRGB space.
+   */
+  BLACK : this.black,
+  
+  /**
+   * The color red.  In the default sRGB space.
+   */
+  red : Raphael.getRGB("rgb(255, 0, 0)"),
+  
+  /**
+   * The color red.  In the default sRGB space.
+   */
+  RED : this.red,
+  
+  /**
+   * The color pink.  In the default sRGB space.
+   */
+  pink : Raphael.getRGB("rgb(255, 175, 175)"),
+  
+  /**
+   * The color pink.  In the default sRGB space.
+   */
+  PINK : this.pink,
+  
+  /**
+   * The color orange.  In the default sRGB space.
+   */
+  orange : Raphael.getRGB("rgb(255, 200, 0)"),
+  
+  /**
+   * The color orange.  In the default sRGB space.
+   */
+  ORANGE : this.orange,
+  
+  /**
+   * The color yellow.  In the default sRGB space.
+   */
+  yellow : Raphael.getRGB("rgb(255, 255, 0)"),
+  
+  /**
+   * The color yellow.  In the default sRGB space.
+   */
+  YELLOW : this.yellow,
+  
+  /**
+   * The color green.  In the default sRGB space.
+   */
+  green : Raphael.getRGB("rgb(0, 255, 0)"),
+  
+  /**
+   * The color green.  In the default sRGB space.
+   */
+  GREEN : this.green,
+  
+  /**
+   * The color magenta.  In the default sRGB space.
+   */
+  magenta : Raphael.getRGB("rgb(255, 0, 255)"),
+  
+  /**
+   * The color magenta.  In the default sRGB space.
+   */
+  MAGENTA : this.magenta,
+  
+  /**
+   * The color cyan.  In the default sRGB space.
+   */
+  cyan : Raphael.getRGB("rgb(0, 255, 255)"),
+  
+  /**
+   * The color cyan.  In the default sRGB space.
+   */
+  CYAN : this.cyan,
+  
+  /**
+   * The color blue.  In the default sRGB space.
+   */
+  blue : Raphael.getRGB("rgb(0, 0, 255)"),
+  
+  /**
+   * The color blue.  In the default sRGB space.
+   */
+  BLUE : this.blue,
+  
+  /************************************************************************/
+
+  // http://www.stm.dp.ua/web-design/color-html.php
+  
+	Snow			:   Raphael.getRGB("#FFFAFA	"),  // 	255 250 250
+	GhostWhite		:   Raphael.getRGB("#F8F8FF	"),  // 	248 248 255
+	WhiteSmoke		:   Raphael.getRGB("#F5F5F5	"),  // 	245 245 245
+	Gainsboro		:   Raphael.getRGB("#DCDCDC	"),  // 	220 220 220
+	FloralWhite		:   Raphael.getRGB("#FFFAF0	"),  // 	255 250 240
+	OldLace			:   Raphael.getRGB("#FDF5E6	"),  // 	253 245 230
+	Linen			:   Raphael.getRGB("#FAF0E6	"),  // 	250 240 230
+	AntiqueWhite	:   Raphael.getRGB("#FAEBD7	"),  // 	250 235 215
+	PapayaWhip		:   Raphael.getRGB("#FFEFD5	"),  // 	255 239 213
+	BlanchedAlmond	:   Raphael.getRGB("#FFEBCD	"),  // 	255 235 205
+	Bisque			:   Raphael.getRGB("#FFE4C4	"),  // 	255 228 196
+	PeachPuff		:   Raphael.getRGB("#FFDAB9	"),  // 	255 218 185
+	NavajoWhite		:   Raphael.getRGB("#FFDEAD	"),  // 	255 222 173
+	Moccasin		:   Raphael.getRGB("#FFE4B5	"),  // 	255 228 181
+	Cornsilk		:   Raphael.getRGB("#FFF8DC	"),  // 	255 248 220
+	Ivory			:   Raphael.getRGB("#FFFFF0	"),  // 	255 255 240
+	LemonChiffon	:   Raphael.getRGB("#FFFACD	"),  // 	255 250 205
+	Seashell		:   Raphael.getRGB("#FFF5EE	"),  // 	255 245 238
+	Honeydew		:   Raphael.getRGB("#F0FFF0	"),  // 	240 255 240
+	MintCream		:   Raphael.getRGB("#F5FFFA	"),  // 	245 255 250
+	Azure			:   Raphael.getRGB("#F0FFFF	"),  // 	240 255 255
+	AliceBlue		:   Raphael.getRGB("#F0F8FF	"),  // 	240 248 255
+	lavender		:   Raphael.getRGB("#E6E6FA	"),  // 	230 230 250
+	LavenderBlush	:   Raphael.getRGB("#FFF0F5	"),  // 	255 240 245
+	MistyRose		:   Raphael.getRGB("#FFE4E1	"),  // 	255 228 225
+	White			:   Raphael.getRGB("#FFFFFF	"),  // 	255 255 255
+	Black			:   Raphael.getRGB("#000000	"),  // 	0 0 0
+	DarkSlateGray	:   Raphael.getRGB("#2F4F4F	"),  // 	47 79 79
+	DimGrey			:   Raphael.getRGB("#696969	"),  // 	105 105 105
+	SlateGrey		:   Raphael.getRGB("#708090	"),  // 	112 128 144
+	LightSlateGray	:   Raphael.getRGB("#778899	"),  // 	119 136 153
+	Grey			:   Raphael.getRGB("#BEBEBE	"),  // 	190 190 190
+	LightGray		:   Raphael.getRGB("#D3D3D3	"),  // 	211 211 211
+	MidnightBlue	:   Raphael.getRGB("#191970	"),  // 	25 25 112
+	NavyBlue		:   Raphael.getRGB("#000080	"),  // 	0 0 128
+	CornflowerBlue	:   Raphael.getRGB("#6495ED	"),  // 	100 149 237
+	DarkSlateBlue	:   Raphael.getRGB("#483D8B	"),  // 	72 61 139
+	SlateBlue		:   Raphael.getRGB("#6A5ACD	"),  // 	106 90 205
+	MediumSlateBlue	:   Raphael.getRGB("#7B68EE	"),  // 	123 104 238
+	LightSlateBlue	:   Raphael.getRGB("#8470FF	"),  // 	132 112 255
+	MediumBlue		:   Raphael.getRGB("#0000CD	"),  // 	0 0 205
+	RoyalBlue		:   Raphael.getRGB("#4169E1	"),  // 	65 105 225
+	Blue			:   Raphael.getRGB("#0000FF	"),  // 	0 0 255
+	DodgerBlue		:   Raphael.getRGB("#1E90FF	"),  // 	30 144 255
+	DeepSkyBlue		:   Raphael.getRGB("#00BFFF	"),  // 	0 191 255
+	SkyBlue			:   Raphael.getRGB("#87CEEB	"),  // 	135 206 235
+	LightSkyBlue	:   Raphael.getRGB("#87CEFA	"),  // 	135 206 250
+	SteelBlue		:   Raphael.getRGB("#4682B4	"),  // 	70 130 180
+	LightSteelBlue	:   Raphael.getRGB("#B0C4DE	"),  // 	176 196 222
+	LightBlue		:   Raphael.getRGB("#ADD8E6	"),  // 	173 216 230
+	PowderBlue		:   Raphael.getRGB("#B0E0E6	"),  // 	176 224 230
+	PaleTurquoise	:   Raphael.getRGB("#AFEEEE	"),  // 	175 238 238
+	DarkTurquoise	:   Raphael.getRGB("#00CED1	"),  // 	0 206 209
+	MediumTurquoise	:   Raphael.getRGB("#48D1CC	"),  // 	72 209 204
+	Turquoise		:   Raphael.getRGB("#40E0D0	"),  // 	64 224 208
+	Cyan			:   Raphael.getRGB("#00FFFF	"),  // 	0 255 255
+	LightCyan		:   Raphael.getRGB("#E0FFFF	"),  // 	224 255 255
+	CadetBlue		:   Raphael.getRGB("#5F9EA0	"),  // 	95 158 160
+	MediumAquamarine:   Raphael.getRGB("#66CDAA	"),  // 	102 205 170
+	Aquamarine		:   Raphael.getRGB("#7FFFD4	"),  // 	127 255 212
+	DarkGreen		:   Raphael.getRGB("#006400	"),  // 	0 100 0
+	DarkOliveGreen	:   Raphael.getRGB("#556B2F	"),  // 	85 107 47
+	DarkSeaGreen	:   Raphael.getRGB("#8FBC8F	"),  // 	143 188 143
+	SeaGreen		:   Raphael.getRGB("#2E8B57	"),  // 	46 139 87
+	MediumSeaGreen	:   Raphael.getRGB("#3CB371	"),  // 	60 179 113
+	LightSeaGreen	:   Raphael.getRGB("#20B2AA	"),  // 	32 178 170
+	PaleGreen		:   Raphael.getRGB("#98FB98	"),  // 	152 251 152
+	SpringGreen		:   Raphael.getRGB("#00FF7F	"),  // 	0 255 127
+	LawnGreen		:   Raphael.getRGB("#7CFC00	"),  // 	124 252 0
+	Green			:   Raphael.getRGB("#00FF00	"),  // 	0 255 0
+	Chartreuse		:   Raphael.getRGB("#7FFF00	"),  // 	127 255 0
+	MedSpringGreen	:   Raphael.getRGB("#00FA9A	"),  // 	0 250 154
+	GreenYellow		:   Raphael.getRGB("#ADFF2F	"),  // 	173 255 47
+	LimeGreen		:   Raphael.getRGB("#32CD32	"),  // 	50 205 50
+	YellowGreen		:   Raphael.getRGB("#9ACD32	"),  // 	154 205 50
+	ForestGreen		:   Raphael.getRGB("#228B22	"),  // 	34 139 34
+	OliveDrab		:   Raphael.getRGB("#6B8E23	"),  // 	107 142 35
+	DarkKhaki		:   Raphael.getRGB("#BDB76B	"),  // 	189 183 107
+	PaleGoldenrod	:   Raphael.getRGB("#EEE8AA	"),  // 	238 232 170
+	LtGoldenrodYello:   Raphael.getRGB("#FAFAD2	"),  // 	250 250 210
+	LightYellow		:   Raphael.getRGB("#FFFFE0	"),  // 	255 255 224
+	Yellow			:   Raphael.getRGB("#FFFF00	"),  // 	255 255 0
+	Gold			:   Raphael.getRGB("#FFD700	"),  // 	255 215 0
+	LightGoldenrod	:   Raphael.getRGB("#EEDD82	"),  // 	238 221 130
+	goldenrod		:   Raphael.getRGB("#DAA520	"),  // 	218 165 32
+	DarkGoldenrod	:   Raphael.getRGB("#B8860B	"),  // 	184 134 11
+	RosyBrown		:   Raphael.getRGB("#BC8F8F	"),  // 	188 143 143
+	IndianRed		:   Raphael.getRGB("#CD5C5C	"),  // 	205 92 92
+	SaddleBrown		:   Raphael.getRGB("#8B4513	"),  // 	139 69 19
+	Sienna			:   Raphael.getRGB("#A0522D	"),  // 	160 82 45
+	Peru			:   Raphael.getRGB("#CD853F	"),  // 	205 133 63
+	Burlywood		:   Raphael.getRGB("#DEB887	"),  // 	222 184 135
+	Beige			:   Raphael.getRGB("#F5F5DC	"),  // 	245 245 220
+	Wheat			:   Raphael.getRGB("#F5DEB3	"),  // 	245 222 179
+	SandyBrown		:   Raphael.getRGB("#F4A460	"),  // 	244 164 96
+	Tan				:   Raphael.getRGB("#D2B48C	"),  // 	210 180 140
+	Chocolate		:   Raphael.getRGB("#D2691E	"),  // 	210 105 30
+	Firebrick		:   Raphael.getRGB("#B22222	"),  // 	178 34 34
+	Brown			:   Raphael.getRGB("#A52A2A	"),  // 	165 42 42
+	DarkSalmon		:   Raphael.getRGB("#E9967A	"),  // 	233 150 122
+	Salmon			:   Raphael.getRGB("#FA8072	"),  // 	250 128 114
+	LightSalmon		:   Raphael.getRGB("#FFA07A	"),  // 	255 160 122
+	Orange			:   Raphael.getRGB("#FFA500	"),  // 	255 165 0
+	DarkOrange		:   Raphael.getRGB("#FF8C00	"),  // 	255 140 0
+	Coral			:   Raphael.getRGB("#FF7F50	"),  // 	255 127 80
+	LightCoral		:   Raphael.getRGB("#F08080	"),  // 	240 128 128
+	Tomato			:   Raphael.getRGB("#FF6347	"),  // 	255 99 71
+	OrangeRed		:   Raphael.getRGB("#FF4500	"),  // 	255 69 0
+	Red				:   Raphael.getRGB("#FF0000	"),  // 	255 0 0
+	HotPink			:   Raphael.getRGB("#FF69B4	"),  // 	255 105 180
+	DeepPink		:   Raphael.getRGB("#FF1493	"),  // 	255 20 147
+	Pink			:   Raphael.getRGB("#FFC0CB	"),  // 	255 192 203
+	LightPink		:   Raphael.getRGB("#FFB6C1	"),  // 	255 182 193
+	PaleVioletRed	:   Raphael.getRGB("#DB7093	"),  // 	219 112 147
+	Maroon			:   Raphael.getRGB("#B03060	"),  // 	176 48 96
+	MediumVioletRed	:   Raphael.getRGB("#C71585	"),  // 	199 21 133
+	VioletRed		:   Raphael.getRGB("#D02090	"),  // 	208 32 144
+	Magenta			:   Raphael.getRGB("#FF00FF	"),  // 	255 0 255
+	Violet			:   Raphael.getRGB("#EE82EE	"),  // 	238 130 238
+	Plum			:   Raphael.getRGB("#DDA0DD	"),  // 	221 160 221
+	Orchid			:   Raphael.getRGB("#DA70D6	"),  // 	218 112 214
+	MediumOrchid	:   Raphael.getRGB("#BA55D3	"),  // 	186 85 211
+	DarkOrchid		:   Raphael.getRGB("#9932CC	"),  // 	153 50 204
+	DarkViolet		:   Raphael.getRGB("#9400D3	"),  // 	148 0 211
+	BlueViolet		:   Raphael.getRGB("#8A2BE2	"),  // 	138 43 226
+	Purple			:   Raphael.getRGB("#A020F0	"),  // 	160 32 240
+	MediumPurple	:   Raphael.getRGB("#9370DB	"),  // 	147 112 219
+	Thistle			:   Raphael.getRGB("#D8BFD8	"),  // 	216 191 216
+	Snow1			:   Raphael.getRGB("#FFFAFA	"),  // 	255 250 250
+	Snow2			:   Raphael.getRGB("#EEE9E9	"),  // 	238 233 233
+	Snow3			:   Raphael.getRGB("#CDC9C9	"),  // 	205 201 201
+	Snow4			:   Raphael.getRGB("#8B8989	"),  // 	139 137 137
+	Seashell1		:   Raphael.getRGB("#FFF5EE	"),  // 	255 245 238
+	Seashell2		:   Raphael.getRGB("#EEE5DE	"),  // 	238 229 222
+	Seashell3		:   Raphael.getRGB("#CDC5BF	"),  // 	205 197 191
+	Seashell4		:   Raphael.getRGB("#8B8682	"),  // 	139 134 130
+	AntiqueWhite1	:   Raphael.getRGB("#FFEFDB	"),  // 	255 239 219
+	AntiqueWhite2	:   Raphael.getRGB("#EEDFCC	"),  // 	238 223 204
+	AntiqueWhite3	:   Raphael.getRGB("#CDC0B0	"),  // 	205 192 176
+	AntiqueWhite4	:   Raphael.getRGB("#8B8378	"),  // 	139 131 120
+	Bisque1			:   Raphael.getRGB("#FFE4C4	"),  // 	255 228 196
+	Bisque2			:   Raphael.getRGB("#EED5B7	"),  // 	238 213 183
+	Bisque3			:   Raphael.getRGB("#CDB79E	"),  // 	205 183 158
+	Bisque4			:   Raphael.getRGB("#8B7D6B	"),  // 	139 125 107
+	PeachPuff1		:   Raphael.getRGB("#FFDAB9	"),  // 	255 218 185
+	PeachPuff2		:   Raphael.getRGB("#EECBAD	"),  // 	238 203 173
+	PeachPuff3		:   Raphael.getRGB("#CDAF95	"),  // 	205 175 149
+	PeachPuff4		:   Raphael.getRGB("#8B7765	"),  // 	139 119 101
+	NavajoWhite1	:   Raphael.getRGB("#FFDEAD	"),  // 	255 222 173
+	NavajoWhite2	:   Raphael.getRGB("#EECFA1	"),  // 	238 207 161
+	NavajoWhite3	:   Raphael.getRGB("#CDB38B	"),  // 	205 179 139
+	NavajoWhite4	:   Raphael.getRGB("#8B795E	"),  // 	139 121 94
+	LemonChiffon1	:   Raphael.getRGB("#FFFACD	"),  // 	255 250 205
+	LemonChiffon2	:   Raphael.getRGB("#EEE9BF	"),  // 	238 233 191
+	LemonChiffon3	:   Raphael.getRGB("#CDC9A5	"),  // 	205 201 165
+	LemonChiffon4	:   Raphael.getRGB("#8B8970	"),  // 	139 137 112
+	Cornsilk1		:   Raphael.getRGB("#FFF8DC	"),  // 	255 248 220
+	Cornsilk2		:   Raphael.getRGB("#EEE8CD	"),  // 	238 232 205
+	Cornsilk3		:   Raphael.getRGB("#CDC8B1	"),  // 	205 200 177
+	Cornsilk4		:   Raphael.getRGB("#8B8878	"),  // 	139 136 120
+	Ivory1			:   Raphael.getRGB("#FFFFF0	"),  // 	255 255 240
+	Ivory2			:   Raphael.getRGB("#EEEEE0	"),  // 	238 238 224
+	Ivory3			:   Raphael.getRGB("#CDCDC1	"),  // 	205 205 193
+	Ivory4			:   Raphael.getRGB("#8B8B83	"),  // 	139 139 131
+	Honeydew1		:   Raphael.getRGB("#F0FFF0	"),  // 	240 255 240
+	Honeydew2		:   Raphael.getRGB("#E0EEE0	"),  // 	224 238 224
+	Honeydew3		:   Raphael.getRGB("#C1CDC1	"),  // 	193 205 193
+	Honeydew4		:   Raphael.getRGB("#838B83	"),  // 	131 139 131
+	LavenderBlush1	:   Raphael.getRGB("#FFF0F5	"),  // 	255 240 245
+	LavenderBlush2	:   Raphael.getRGB("#EEE0E5	"),  // 	238 224 229
+	LavenderBlush3	:   Raphael.getRGB("#CDC1C5	"),  // 	205 193 197
+	LavenderBlush4	:   Raphael.getRGB("#8B8386	"),  // 	139 131 134
+	MistyRose1		:   Raphael.getRGB("#FFE4E1	"),  // 	255 228 225
+	MistyRose2		:   Raphael.getRGB("#EED5D2	"),  // 	238 213 210
+	MistyRose3		:   Raphael.getRGB("#CDB7B5	"),  // 	205 183 181
+	MistyRose4		:   Raphael.getRGB("#8B7D7B	"),  // 	139 125 123
+	Azure1			:   Raphael.getRGB("#F0FFFF	"),  // 	240 255 255
+	Azure2			:   Raphael.getRGB("#E0EEEE	"),  // 	224 238 238
+	Azure3			:   Raphael.getRGB("#C1CDCD	"),  // 	193 205 205
+	Azure4			:   Raphael.getRGB("#838B8B	"),  // 	131 139 139
+	SlateBlue1		:   Raphael.getRGB("#836FFF	"),  // 	131 111 255
+	SlateBlue2		:   Raphael.getRGB("#7A67EE	"),  // 	122 103 238
+	SlateBlue3		:   Raphael.getRGB("#6959CD	"),  // 	105 89 205
+	SlateBlue4		:   Raphael.getRGB("#473C8B	"),  // 	71 60 139
+	RoyalBlue1		:   Raphael.getRGB("#4876FF	"),  // 	72 118 255
+	RoyalBlue2		:   Raphael.getRGB("#436EEE	"),  // 	67 110 238
+	RoyalBlue3		:   Raphael.getRGB("#3A5FCD	"),  // 	58 95 205
+	RoyalBlue4		:   Raphael.getRGB("#27408B	"),  // 	39 64 139
+	Blue1			:   Raphael.getRGB("#0000FF	"),  // 	0 0 255
+	Blue2			:   Raphael.getRGB("#0000EE	"),  // 	0 0 238
+	Blue3			:   Raphael.getRGB("#0000CD	"),  // 	0 0 205
+	Blue4			:   Raphael.getRGB("#00008B	"),  // 	0 0 139
+	DodgerBlue1		:   Raphael.getRGB("#1E90FF	"),  // 	30 144 255
+	DodgerBlue2		:   Raphael.getRGB("#1C86EE	"),  // 	28 134 238
+	DodgerBlue3		:   Raphael.getRGB("#1874CD	"),  // 	24 116 205
+	DodgerBlue4		:   Raphael.getRGB("#104E8B	"),  // 	16 78 139
+	SteelBlue1		:   Raphael.getRGB("#63B8FF	"),  // 	99 184 255
+	SteelBlue2		:   Raphael.getRGB("#5CACEE	"),  // 	92 172 238
+	SteelBlue3		:   Raphael.getRGB("#4F94CD	"),  // 	79 148 205
+	SteelBlue4		:   Raphael.getRGB("#36648B	"),  // 	54 100 139
+	DeepSkyBlue1	:   Raphael.getRGB("#00BFFF	"),  // 	0 191 255
+	DeepSkyBlue2	:   Raphael.getRGB("#00B2EE	"),  // 	0 178 238
+	DeepSkyBlue3	:   Raphael.getRGB("#009ACD	"),  // 	0 154 205
+	DeepSkyBlue4	:   Raphael.getRGB("#00688B	"),  // 	0 104 139
+	SkyBlue1		:   Raphael.getRGB("#87CEFF	"),  // 	135 206 255
+	SkyBlue2		:   Raphael.getRGB("#7EC0EE	"),  // 	126 192 238
+	SkyBlue3		:   Raphael.getRGB("#6CA6CD	"),  // 	108 166 205
+	SkyBlue4		:   Raphael.getRGB("#4A708B	"),  // 	74 112 139
+	LightSkyBlue1	:   Raphael.getRGB("#B0E2FF	"),  // 	176 226 255
+	LightSkyBlue2	:   Raphael.getRGB("#A4D3EE	"),  // 	164 211 238
+	LightSkyBlue3	:   Raphael.getRGB("#8DB6CD	"),  // 	141 182 205
+	LightSkyBlue4	:   Raphael.getRGB("#607B8B	"),  // 	96 123 139
+	SlateGray1		:   Raphael.getRGB("#C6E2FF	"),  // 	198 226 255
+	SlateGray2		:   Raphael.getRGB("#B9D3EE	"),  // 	185 211 238
+	SlateGray3		:   Raphael.getRGB("#9FB6CD	"),  // 	159 182 205
+	SlateGray4		:   Raphael.getRGB("#6C7B8B	"),  // 	108 123 139
+	LightSteelBlue1	:   Raphael.getRGB("#CAE1FF	"),  // 	202 225 255
+	LightSteelBlue2	:   Raphael.getRGB("#BCD2EE	"),  // 	188 210 238
+	LightSteelBlue3	:   Raphael.getRGB("#A2B5CD	"),  // 	162 181 205
+	LightSteelBlue4	:   Raphael.getRGB("#6E7B8B	"),  // 	110 123 139
+	LightBlue1		:   Raphael.getRGB("#BFEFFF	"),  // 	191 239 255
+	LightBlue2		:   Raphael.getRGB("#B2DFEE	"),  // 	178 223 238
+	LightBlue3		:   Raphael.getRGB("#9AC0CD	"),  // 	154 192 205
+	LightBlue4		:   Raphael.getRGB("#68838B	"),  // 	104 131 139
+	LightCyan1		:   Raphael.getRGB("#E0FFFF	"),  // 	224 255 255
+	LightCyan2		:   Raphael.getRGB("#D1EEEE	"),  // 	209 238 238
+	LightCyan3		:   Raphael.getRGB("#B4CDCD	"),  // 	180 205 205
+	LightCyan4		:   Raphael.getRGB("#7A8B8B	"),  // 	122 139 139
+	PaleTurquoise1	:   Raphael.getRGB("#BBFFFF	"),  // 	187 255 255
+	PaleTurquoise2	:   Raphael.getRGB("#AEEEEE	"),  // 	174 238 238
+	PaleTurquoise3	:   Raphael.getRGB("#96CDCD	"),  // 	150 205 205
+	PaleTurquoise4	:   Raphael.getRGB("#668B8B	"),  // 	102 139 139
+	CadetBlue1		:   Raphael.getRGB("#98F5FF	"),  // 	152 245 255
+	CadetBlue2		:   Raphael.getRGB("#8EE5EE	"),  // 	142 229 238
+	CadetBlue3		:   Raphael.getRGB("#7AC5CD	"),  // 	122 197 205
+	CadetBlue4		:   Raphael.getRGB("#53868B	"),  // 	83 134 139
+	Turquoise1		:   Raphael.getRGB("#00F5FF	"),  // 	0 245 255
+	Turquoise2		:   Raphael.getRGB("#00E5EE	"),  // 	0 229 238
+	Turquoise3		:   Raphael.getRGB("#00C5CD	"),  // 	0 197 205
+	Turquoise4		:   Raphael.getRGB("#00868B	"),  // 	0 134 139
+	Cyan1			:   Raphael.getRGB("#00FFFF	"),  // 	0 255 255
+	Cyan2			:   Raphael.getRGB("#00EEEE	"),  // 	0 238 238
+	Cyan3			:   Raphael.getRGB("#00CDCD	"),  // 	0 205 205
+	Cyan4			:   Raphael.getRGB("#008B8B	"),  // 	0 139 139
+	DarkSlateGray1	:   Raphael.getRGB("#97FFFF	"),  // 	151 255 255
+	DarkSlateGray2	:   Raphael.getRGB("#8DEEEE	"),  // 	141 238 238
+	DarkSlateGray3	:   Raphael.getRGB("#79CDCD	"),  // 	121 205 205
+	DarkSlateGray4	:   Raphael.getRGB("#528B8B	"),  // 	82 139 139
+	Aquamarine1		:   Raphael.getRGB("#7FFFD4	"),  // 	127 255 212
+	Aquamarine2		:   Raphael.getRGB("#76EEC6	"),  // 	118 238 198
+	Aquamarine3		:   Raphael.getRGB("#66CDAA	"),  // 	102 205 170
+	Aquamarine4		:   Raphael.getRGB("#458B74	"),  // 	69 139 116
+	DarkSeaGreen1	:   Raphael.getRGB("#C1FFC1	"),  // 	193 255 193
+	DarkSeaGreen2	:   Raphael.getRGB("#B4EEB4	"),  // 	180 238 180
+	DarkSeaGreen3	:   Raphael.getRGB("#9BCD9B	"),  // 	155 205 155
+	DarkSeaGreen4	:   Raphael.getRGB("#698B69	"),  // 	105 139 105
+	SeaGreen1		:   Raphael.getRGB("#54FF9F	"),  // 	84 255 159
+	SeaGreen2		:   Raphael.getRGB("#4EEE94	"),  // 	78 238 148
+	SeaGreen3		:   Raphael.getRGB("#43CD80	"),  // 	67 205 128
+	SeaGreen4		:   Raphael.getRGB("#2E8B57	"),  // 	46 139 87
+	PaleGreen1		:   Raphael.getRGB("#9AFF9A	"),  // 	154 255 154
+	PaleGreen2		:   Raphael.getRGB("#90EE90	"),  // 	144 238 144
+	PaleGreen3		:   Raphael.getRGB("#7CCD7C	"),  // 	124 205 124
+	PaleGreen4		:   Raphael.getRGB("#548B54	"),  // 	84 139 84
+	SpringGreen1	:   Raphael.getRGB("#00FF7F	"),  // 	0 255 127
+	SpringGreen2	:   Raphael.getRGB("#00EE76	"),  // 	0 238 118
+	SpringGreen3	:   Raphael.getRGB("#00CD66	"),  // 	0 205 102
+	SpringGreen4	:   Raphael.getRGB("#008B45	"),  // 	0 139 69
+	Green1			:   Raphael.getRGB("#00FF00	"),  // 	0 255 0
+	Green2			:   Raphael.getRGB("#00EE00	"),  // 	0 238 0
+	Green3			:   Raphael.getRGB("#00CD00	"),  // 	0 205 0
+	Green4			:   Raphael.getRGB("#008B00	"),  // 	0 139 0
+	Chartreuse1		:   Raphael.getRGB("#7FFF00	"),  // 	127 255 0
+	Chartreuse2		:   Raphael.getRGB("#76EE00	"),  // 	118 238 0
+	Chartreuse3		:   Raphael.getRGB("#66CD00	"),  // 	102 205 0
+	Chartreuse4		:   Raphael.getRGB("#458B00	"),  // 	69 139 0
+	OliveDrab1		:   Raphael.getRGB("#C0FF3E	"),  // 	192 255 62
+	OliveDrab2		:   Raphael.getRGB("#B3EE3A	"),  // 	179 238 58
+	OliveDrab3		:   Raphael.getRGB("#9ACD32	"),  // 	154 205 50
+	OliveDrab4		:   Raphael.getRGB("#698B22	"),  // 	105 139 34
+	DarkOliveGreen1	:   Raphael.getRGB("#CAFF70	"),  // 	202 255 112
+	DarkOliveGreen2	:   Raphael.getRGB("#BCEE68	"),  // 	188 238 104
+	DarkOliveGreen3	:   Raphael.getRGB("#A2CD5A	"),  // 	162 205 90
+	DarkOliveGreen4	:   Raphael.getRGB("#6E8B3D	"),  // 	110 139 61
+	Khaki1			:   Raphael.getRGB("#FFF68F	"),  // 	255 246 143
+	Khaki2			:   Raphael.getRGB("#EEE685	"),  // 	238 230 133
+	Khaki3			:   Raphael.getRGB("#CDC673	"),  // 	205 198 115
+	Khaki4			:   Raphael.getRGB("#8B864E	"),  // 	139 134 78
+	LightGoldenrod1	:   Raphael.getRGB("#FFEC8B	"),  // 	255 236 139
+	LightGoldenrod2	:   Raphael.getRGB("#EEDC82	"),  // 	238 220 130
+	LightGoldenrod3	:   Raphael.getRGB("#CDBE70	"),  // 	205 190 112
+	LightGoldenrod4	:   Raphael.getRGB("#8B814C	"),  // 	139 129 76
+	LightYellow1	:   Raphael.getRGB("#FFFFE0	"),  // 	255 255 224
+	LightYellow2	:   Raphael.getRGB("#EEEED1	"),  // 	238 238 209
+	LightYellow3	:   Raphael.getRGB("#CDCDB4	"),  // 	205 205 180
+	LightYellow4	:   Raphael.getRGB("#8B8B7A	"),  // 	139 139 122
+	Yellow1			:   Raphael.getRGB("#FFFF00	"),  // 	255 255 0
+	Yellow2			:   Raphael.getRGB("#EEEE00	"),  // 	238 238 0
+	Yellow3			:   Raphael.getRGB("#CDCD00	"),  // 	205 205 0
+	Yellow4			:   Raphael.getRGB("#8B8B00	"),  // 	139 139 0
+	Gold1			:   Raphael.getRGB("#FFD700	"),  // 	255 215 0
+	Gold2			:   Raphael.getRGB("#EEC900	"),  // 	238 201 0
+	Gold3			:   Raphael.getRGB("#CDAD00	"),  // 	205 173 0
+	Gold4			:   Raphael.getRGB("#8B7500	"),  // 	139 117 0
+	Goldenrod1		:   Raphael.getRGB("#FFC125	"),  // 	255 193 37
+	Goldenrod2		:   Raphael.getRGB("#EEB422	"),  // 	238 180 34
+	Goldenrod3		:   Raphael.getRGB("#CD9B1D	"),  // 	205 155 29
+	Goldenrod4		:   Raphael.getRGB("#8B6914	"),  // 	139 105 20
+	DarkGoldenrod1	:   Raphael.getRGB("#FFB90F	"),  // 	255 185 15
+	DarkGoldenrod2	:   Raphael.getRGB("#EEAD0E	"),  // 	238 173 14
+	DarkGoldenrod3	:   Raphael.getRGB("#CD950C	"),  // 	205 149 12
+	DarkGoldenrod4	:   Raphael.getRGB("#8B658B	"),  // 	139 101 8
+	RosyBrown1		:   Raphael.getRGB("#FFC1C1	"),  // 	255 193 193
+	RosyBrown2		:   Raphael.getRGB("#EEB4B4	"),  // 	238 180 180
+	RosyBrown3		:   Raphael.getRGB("#CD9B9B	"),  // 	205 155 155
+	RosyBrown4		:   Raphael.getRGB("#8B6969	"),  // 	139 105 105
+	IndianRed1		:   Raphael.getRGB("#FF6A6A	"),  // 	255 106 106
+	IndianRed2		:   Raphael.getRGB("#EE6363	"),  // 	238 99 99
+	IndianRed3		:   Raphael.getRGB("#CD5555	"),  // 	205 85 85
+	IndianRed4		:   Raphael.getRGB("#8B3A3A	"),  // 	139 58 58
+	Sienna1			:   Raphael.getRGB("#FF8247	"),  // 	255 130 71
+	Sienna2			:   Raphael.getRGB("#EE7942	"),  // 	238 121 66
+	Sienna3			:   Raphael.getRGB("#CD6839	"),  // 	205 104 57
+	Sienna4			:   Raphael.getRGB("#8B4726	"),  // 	139 71 38
+	Burlywood1		:   Raphael.getRGB("#FFD39B	"),  // 	255 211 155
+	Burlywood2		:   Raphael.getRGB("#EEC591	"),  // 	238 197 145
+	Burlywood3		:   Raphael.getRGB("#CDAA7D	"),  // 	205 170 125
+	Burlywood4		:   Raphael.getRGB("#8B7355	"),  // 	139 115 85
+	Wheat1			:   Raphael.getRGB("#FFE7BA	"),  // 	255 231 186
+	Wheat2			:   Raphael.getRGB("#EED8AE	"),  // 	238 216 174
+	Wheat3			:   Raphael.getRGB("#CDBA96	"),  // 	205 186 150
+	Wheat4			:   Raphael.getRGB("#8B7E66	"),  // 	139 126 102
+	Tan1			:   Raphael.getRGB("#FFA54F	"),  // 	255 165 79
+	Tan2			:   Raphael.getRGB("#EE9A49	"),  // 	238 154 73
+	Tan3			:   Raphael.getRGB("#CD853F	"),  // 	205 133 63
+	Tan4			:   Raphael.getRGB("#8B5A2B	"),  // 	139 90 43
+	Chocolate1		:   Raphael.getRGB("#FF7F24	"),  // 	255 127 36
+	Chocolate2		:   Raphael.getRGB("#EE7621	"),  // 	238 118 33
+	Chocolate3		:   Raphael.getRGB("#CD661D	"),  // 	205 102 29
+	Chocolate4		:   Raphael.getRGB("#8B4513	"),  // 	139 69 19
+	Firebrick1		:   Raphael.getRGB("#FF3030	"),  // 	255 48 48
+	Firebrick2		:   Raphael.getRGB("#EE2C2C	"),  // 	238 44 44
+	Firebrick3		:   Raphael.getRGB("#CD2626	"),  // 	205 38 38
+	Firebrick4		:   Raphael.getRGB("#8B1A1A	"),  // 	139 26 26
+	Brown1			:   Raphael.getRGB("#FF4040	"),  // 	255 64 64
+	Brown2			:   Raphael.getRGB("#EE3B3B	"),  // 	238 59 59
+	Brown3			:   Raphael.getRGB("#CD3333	"),  // 	205 51 51
+	Brown4			:   Raphael.getRGB("#8B2323	"),  // 	139 35 35
+	Salmon1			:   Raphael.getRGB("#FF8C69	"),  // 	255 140 105
+	Salmon2			:   Raphael.getRGB("#EE8262	"),  // 	238 130 98
+	Salmon3			:   Raphael.getRGB("#CD7054	"),  // 	205 112 84
+	Salmon4			:   Raphael.getRGB("#8B4C39	"),  // 	139 76 57
+	LightSalmon1	:   Raphael.getRGB("#FFA07A	"),  // 	255 160 122
+	LightSalmon2	:   Raphael.getRGB("#EE9572	"),  // 	238 149 114
+	LightSalmon3	:   Raphael.getRGB("#CD8162	"),  // 	205 129 98
+	LightSalmon4	:   Raphael.getRGB("#8B5742	"),  // 	139 87 66
+	Orange1			:   Raphael.getRGB("#FFA500	"),  // 	255 165 0
+	Orange2			:   Raphael.getRGB("#EE9A00	"),  // 	238 154 0
+	Orange3			:   Raphael.getRGB("#CD8500	"),  // 	205 133 0
+	Orange4			:   Raphael.getRGB("#8B5A00	"),  // 	139 90 0
+	DarkOrange1		:   Raphael.getRGB("#FF7F00	"),  // 	255 127 0
+	DarkOrange2		:   Raphael.getRGB("#EE7600	"),  // 	238 118 0
+	DarkOrange3		:   Raphael.getRGB("#CD6600	"),  // 	205 102 0
+	DarkOrange4		:   Raphael.getRGB("#8B4500	"),  // 	139 69 0
+	Coral1			:   Raphael.getRGB("#FF7256	"),  // 	255 114 86
+	Coral2			:   Raphael.getRGB("#EE6A50	"),  // 	238 106 80
+	Coral3			:   Raphael.getRGB("#CD5B45	"),  // 	205 91 69
+	Coral4			:   Raphael.getRGB("#8B3E2F	"),  // 	139 62 47
+	Tomato1			:   Raphael.getRGB("#FF6347	"),  // 	255 99 71
+	Tomato2			:   Raphael.getRGB("#EE5C42	"),  // 	238 92 66
+	Tomato3			:   Raphael.getRGB("#CD4F39	"),  // 	205 79 57
+	Tomato4			:   Raphael.getRGB("#8B3626	"),  // 	139 54 38
+	OrangeRed1		:   Raphael.getRGB("#FF4500	"),  // 	255 69 0
+	OrangeRed2		:   Raphael.getRGB("#EE4000	"),  // 	238 64 0
+	OrangeRed3		:   Raphael.getRGB("#CD3700	"),  // 	205 55 0
+	OrangeRed4		:   Raphael.getRGB("#8B2500	"),  // 	139 37 0
+	Red1			:   Raphael.getRGB("#FF0000	"),  // 	255 0 0
+	Red2			:   Raphael.getRGB("#EE0000	"),  // 	238 0 0
+	Red3			:   Raphael.getRGB("#CD0000	"),  // 	205 0 0
+	Red4			:   Raphael.getRGB("#8B0000	"),  // 	139 0 0
+	DeepPink1		:   Raphael.getRGB("#FF1493	"),  // 	255 20 147
+	DeepPink2		:   Raphael.getRGB("#EE1289	"),  // 	238 18 137
+	DeepPink3		:   Raphael.getRGB("#CD1076	"),  // 	205 16 118
+	DeepPink4		:   Raphael.getRGB("#8B0A50	"),  // 	139 10 80
+	HotPink1		:   Raphael.getRGB("#FF6EB4	"),  // 	255 110 180
+	HotPink2		:   Raphael.getRGB("#EE6AA7	"),  // 	238 106 167
+	HotPink3		:   Raphael.getRGB("#CD6090	"),  // 	205 96 144
+	HotPink4		:   Raphael.getRGB("#8B3A62	"),  // 	139 58 98
+	Pink1			:   Raphael.getRGB("#FFB5C5	"),  // 	255 181 197
+	Pink2			:   Raphael.getRGB("#EEA9B8	"),  // 	238 169 184
+	Pink3			:   Raphael.getRGB("#CD919E	"),  // 	205 145 158
+	Pink4			:   Raphael.getRGB("#8B636C	"),  // 	139 99 108
+	LightPink1		:   Raphael.getRGB("#FFAEB9	"),  // 	255 174 185
+	LightPink2		:   Raphael.getRGB("#EEA2AD	"),  // 	238 162 173
+	LightPink3		:   Raphael.getRGB("#CD8C95	"),  // 	205 140 149
+	LightPink4		:   Raphael.getRGB("#8B5F65	"),  // 	139 95 101
+	PaleVioletRed1	:   Raphael.getRGB("#FF82AB	"),  // 	255 130 171
+	PaleVioletRed2	:   Raphael.getRGB("#EE799F	"),  // 	238 121 159
+	PaleVioletRed3	:   Raphael.getRGB("#CD6889	"),  // 	205 104 137
+	PaleVioletRed4	:   Raphael.getRGB("#8B475D	"),  // 	139 71 93
+	Maroon1			:   Raphael.getRGB("#FF34B3	"),  // 	255 52 179
+	Maroon2			:   Raphael.getRGB("#EE30A7	"),  // 	238 48 167
+	Maroon3			:   Raphael.getRGB("#CD2990	"),  // 	205 41 144
+	Maroon4			:   Raphael.getRGB("#8B1C62	"),  // 	139 28 98
+	VioletRed1		:   Raphael.getRGB("#FF3E96	"),  // 	255 62 150
+	VioletRed2		:   Raphael.getRGB("#EE3A8C	"),  // 	238 58 140
+	VioletRed3		:   Raphael.getRGB("#CD3278	"),  // 	205 50 120
+	VioletRed4		:   Raphael.getRGB("#8B2252	"),  // 	139 34 82
+	Magenta1		:   Raphael.getRGB("#FF00FF	"),  // 	255 0 255
+	Magenta2		:   Raphael.getRGB("#EE00EE	"),  // 	238 0 238
+	Magenta3		:   Raphael.getRGB("#CD00CD	"),  // 	205 0 205
+	Magenta4		:   Raphael.getRGB("#8B008B	"),  // 	139 0 139
+	Orchid1			:   Raphael.getRGB("#FF83FA	"),  // 	255 131 250
+	Orchid2			:   Raphael.getRGB("#EE7AE9	"),  // 	238 122 233
+	Orchid3			:   Raphael.getRGB("#CD69C9	"),  // 	205 105 201
+	Orchid4			:   Raphael.getRGB("#8B4789	"),  // 	139 71 137
+	Plum1			:   Raphael.getRGB("#FFBBFF	"),  // 	255 187 255
+	Plum2			:   Raphael.getRGB("#EEAEEE	"),  // 	238 174 238
+	Plum3			:   Raphael.getRGB("#CD96CD	"),  // 	205 150 205
+	Plum4			:   Raphael.getRGB("#8B668B	"),  // 	139 102 139
+	MediumOrchid1	:   Raphael.getRGB("#E066FF	"),  // 	224 102 255
+	MediumOrchid2	:   Raphael.getRGB("#D15FEE	"),  // 	209 95 238
+	MediumOrchid3	:   Raphael.getRGB("#B452CD	"),  // 	180 82 205
+	MediumOrchid4	:   Raphael.getRGB("#7A378B	"),  // 	122 55 139
+	DarkOrchid1		:   Raphael.getRGB("#BF3EFF	"),  // 	191 62 255
+	DarkOrchid2		:   Raphael.getRGB("#B23AEE	"),  // 	178 58 238
+	DarkOrchid3		:   Raphael.getRGB("#9A32CD	"),  // 	154 50 205
+	DarkOrchid4		:   Raphael.getRGB("#68228B	"),  // 	104 34 139
+	Purple1			:   Raphael.getRGB("#9B30FF	"),  // 	155 48 255
+	Purple2			:   Raphael.getRGB("#912CEE	"),  // 	145 44 238
+	Purple3			:   Raphael.getRGB("#7D26CD	"),  // 	125 38 205
+	Purple4			:   Raphael.getRGB("#551A8B	"),  // 	85 26 139
+	MediumPurple1	:   Raphael.getRGB("#AB82FF	"),  // 	171 130 255
+	MediumPurple2	:   Raphael.getRGB("#9F79EE	"),  // 	159 121 238
+	MediumPurple3	:   Raphael.getRGB("#8968CD	"),  // 	137 104 205
+	MediumPurple4	:   Raphael.getRGB("#5D478B	"),  // 	93 71 139
+	Thistle1		:   Raphael.getRGB("#FFE1FF	"),  // 	255 225 255
+	Thistle2		:   Raphael.getRGB("#EED2EE	"),  // 	238 210 238
+	Thistle3		:   Raphael.getRGB("#CDB5CD	"),  // 	205 181 205
+	Thistle4		:   Raphael.getRGB("#8B7B8B	"),  // 	139 123 139
+	grey11			:   Raphael.getRGB("#1C1C1C	"),  // 	28 28 28
+	grey21			:   Raphael.getRGB("#363636	"),  // 	54 54 54
+	grey31			:   Raphael.getRGB("#4F4F4F	"),  // 	79 79 79
+	grey41			:   Raphael.getRGB("#696969	"),  // 	105 105 105
+	grey51			:   Raphael.getRGB("#828282	"),  // 	130 130 130
+	grey61			:   Raphael.getRGB("#9C9C9C	"),  // 	156 156 156
+	grey71			:   Raphael.getRGB("#B5B5B5	"),  // 	181 181 181
+	gray81			:   Raphael.getRGB("#CFCFCF	"),  // 	207 207 207
+	gray91			:   Raphael.getRGB("#E8E8E8	"),  // 	232 232 232
+	DarkGrey		:   Raphael.getRGB("#A9A9A9	"),  // 	169 169 169
+	DarkBlue		:   Raphael.getRGB("#00008B	"),  // 	0 0 139
+	DarkCyan		:   Raphael.getRGB("#008B8B	"),  // 	0 139 139
+	DarkMagenta		:   Raphael.getRGB("#8B008B	"),  // 	139 0 139
+	DarkRed			:   Raphael.getRGB("#8B0000	"),  // 	139 0 0
+	LightGreen		:   Raphael.getRGB("#90EE90	"),  // 	144 238 144
+
+  
+  
+  get: function(R, G, B){
+	return Raphael.getRGB("rgb(" + R + ", " + G + ", " + B + ")");
+  }
+};

+ 270 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/LineBreakMeasurer.js

@@ -0,0 +1,270 @@
+/**
+ * Word wrapping
+ * 
+ * @author (Javascript) Dmitry Farafonov
+ */
+
+		var AttributedStringIterator = function(text){
+				//this.text = this.rtrim(this.ltrim(text));
+				text = text.replace(/(\s)+/, " ");
+				this.text = this.rtrim(text);
+				/*
+				if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) {
+					throw new IllegalArgumentException("Invalid substring range");
+				}
+				*/
+				this.beginIndex = 0;
+				this.endIndex = this.text.length;
+				this.currentIndex = this.beginIndex;
+				
+				//console.group("[AttributedStringIterator]");
+				var i = 0;
+				var string = this.text;
+				var fullPos = 0;
+				
+				//console.log("string: \"" + string + "\", length: " + string.length);
+				this.startWordOffsets = [];
+				this.startWordOffsets.push(fullPos);
+				
+				// TODO: remove i 1000
+				while (i<1000) {
+					var pos = string.search(/[ \t\n\f-\.\,]/);
+					if (pos == -1)
+						break;
+					
+					// whitespace start
+					fullPos += pos;
+					string = string.substr(pos);
+					////console.log("fullPos: " + fullPos + ", pos: " + pos +  ", string: ", string);
+					
+					// remove whitespaces
+					var pos = string.search(/[^ \t\n\f-\.\,]/);
+					if (pos == -1)
+						break;
+						
+					// whitespace end
+					fullPos += pos;
+					string = string.substr(pos);
+					
+					////console.log("fullPos: " + fullPos);
+					this.startWordOffsets.push(fullPos);
+					
+					i++;
+				}
+				//console.log("startWordOffsets: ", this.startWordOffsets);
+				//console.groupEnd();
+			};
+			AttributedStringIterator.prototype = {
+				getEndIndex: function(pos){
+					if (typeof(pos) == "undefined")
+						return this.endIndex;
+						
+					var string = this.text.substr(pos, this.endIndex - pos);
+					
+					var posEndOfLine = string.search(/[\n]/);
+					if (posEndOfLine == -1)
+						return this.endIndex;
+					else
+						return pos + posEndOfLine;
+				},
+				getBeginIndex: function(){
+					return this.beginIndex;
+				},
+				isWhitespace: function(pos){
+					var str = this.text[pos];
+					var whitespaceChars = " \t\n\f";
+					
+					return (whitespaceChars.indexOf(str) != -1);
+				},
+				isNewLine: function(pos){
+					var str = this.text[pos];
+					var whitespaceChars = "\n";
+					
+					return (whitespaceChars.indexOf(str) != -1);
+				},
+				preceding: function(pos){
+					//console.group("[AttributedStringIterator.preceding]");
+					for(var i in this.startWordOffsets) {
+						var startWordOffset = this.startWordOffsets[i];
+						if (pos < startWordOffset && i>0) {
+							//console.log("startWordOffset: " + this.startWordOffsets[i-1]);
+							//console.groupEnd();
+							return this.startWordOffsets[i-1];
+						}
+					}
+					//console.log("pos: " + pos);
+					//console.groupEnd();
+					return this.startWordOffsets[i];
+				},
+				following: function(pos){
+					//console.group("[AttributedStringIterator.following]");
+					for(var i in this.startWordOffsets) {
+						var startWordOffset = this.startWordOffsets[i];
+						if (pos < startWordOffset && i>0) {
+							//console.log("startWordOffset: " + this.startWordOffsets[i]);
+							//console.groupEnd();
+							return this.startWordOffsets[i];
+						}
+					}
+					//console.log("pos: " + pos);
+					//console.groupEnd();
+					return this.startWordOffsets[i];
+				},
+				ltrim: function(str){
+					var patt2=/^\s+/g;
+					return str.replace(patt2, "");
+				}, 
+				rtrim: function(str){
+					var patt2=/\s+$/g;
+					return str.replace(patt2, "");
+				},
+				getLayout: function(start, limit){
+					return this.text.substr(start, limit - start);
+				},
+				getCharAtPos: function(pos) {
+					return this.text[pos];
+				}
+			};
+
+		var LineBreakMeasurer = function(paper, x, y, text, fontAttrs){
+				this.paper = paper;
+				this.text = new AttributedStringIterator(text);
+				this.fontAttrs = fontAttrs;
+				
+				if (this.text.getEndIndex() - this.text.getBeginIndex() < 1) {
+					throw {message: "Text must contain at least one character.", code: "IllegalArgumentException"};
+				}
+				
+				//this.measurer = new TextMeasurer(paper, this.text, this.fontAttrs);
+				this.limit = this.text.getEndIndex();
+				this.pos = this.start = this.text.getBeginIndex();
+				
+				this.rafaelTextObject = this.paper.text(x, y, this.text.text).attr(fontAttrs).attr("text-anchor", "start");
+				this.svgTextObject = this.rafaelTextObject[0];
+			};
+			LineBreakMeasurer.prototype = {
+				nextOffset: function(wrappingWidth, offsetLimit, requireNextWord) {
+					//console.group("[nextOffset]");
+					var nextOffset = this.pos;
+					if (this.pos < this.limit) {
+						if (offsetLimit <= this.pos) {
+							throw {message: "offsetLimit must be after current position", code: "IllegalArgumentException"};
+						}
+						
+						var charAtMaxAdvance = this.getLineBreakIndex(this.pos, wrappingWidth);
+						//charAtMaxAdvance --;
+						//console.log("charAtMaxAdvance:", charAtMaxAdvance, ", [" + this.text.getCharAtPos(charAtMaxAdvance) + "]");
+						
+						if (charAtMaxAdvance == this.limit) {
+							nextOffset = this.limit;
+							//console.log("charAtMaxAdvance == this.limit");
+						} else if (this.text.isNewLine(charAtMaxAdvance)) {
+							//console.log("isNewLine");
+							nextOffset = charAtMaxAdvance+1;
+						} else if (this.text.isWhitespace(charAtMaxAdvance)) {
+							// TODO: find next noSpaceChar
+							//return nextOffset;
+							nextOffset = this.text.following(charAtMaxAdvance);
+						} else {
+							// Break is in a word;  back up to previous break.
+							/*
+							var testPos = charAtMaxAdvance + 1;
+							if (testPos == this.limit) {
+								console.error("hbz...");
+							} else {
+								nextOffset = this.text.preceding(charAtMaxAdvance);
+							}
+							*/
+							nextOffset = this.text.preceding(charAtMaxAdvance);
+							
+							if (nextOffset <= this.pos) {
+								nextOffset = Math.max(this.pos+1, charAtMaxAdvance);
+							}
+						}
+					}
+					if (nextOffset > offsetLimit) {
+						nextOffset = offsetLimit;
+					}
+					//console.log("nextOffset: " + nextOffset);
+					//console.groupEnd();
+					return nextOffset;
+				},
+				nextLayout: function(wrappingWidth) {
+					//console.groupCollapsed("[nextLayout]");
+					if (this.pos < this.limit) {
+						var requireNextWord = false;
+						var layoutLimit = this.nextOffset(wrappingWidth, this.limit, requireNextWord);
+						//console.log("layoutLimit:", layoutLimit);
+						if (layoutLimit == this.pos) {
+							//console.groupEnd();
+							return null;
+						}
+						var result = this.text.getLayout(this.pos, layoutLimit);
+						//console.log("layout: \"" + result + "\"");
+						
+						// remove end of line
+						
+						//var posEndOfLine = this.text.getEndIndex(this.pos);
+						//if (posEndOfLine < result.length)
+						//	result = result.substr(0, posEndOfLine);
+						
+						this.pos = layoutLimit;
+						
+						//console.groupEnd();
+						return result;
+					} else {
+						//console.groupEnd();
+						return null;
+					}
+				},
+				getLineBreakIndex: function(pos, wrappingWidth) {
+					//console.group("[getLineBreakIndex]");
+					//console.log("pos:"+pos + ", text: \""+ this.text.text.replace(/\n/g, "_").substr(pos, 1) + "\"");
+					
+					var bb = this.rafaelTextObject.getBBox();
+					
+					var charNum = -1;
+					try {
+						var svgPoint = this.svgTextObject.getStartPositionOfChar(pos);
+						//var dot = this.paper.ellipse(svgPoint.x, svgPoint.y, 1, 1).attr({"stroke-width": 0, fill: Color.blue});
+						svgPoint.x = svgPoint.x + wrappingWidth;
+						//svgPoint.y = bb.y;
+						//console.log("svgPoint:", svgPoint);
+					
+						//var dot = this.paper.ellipse(svgPoint.x, svgPoint.y, 1, 1).attr({"stroke-width": 0, fill: Color.red});
+					
+						charNum = this.svgTextObject.getCharNumAtPosition(svgPoint);
+					} catch (e){
+						console.warn("getStartPositionOfChar error, pos:" + pos);
+						/*
+						var testPos = pos + 1;
+						if (testPos < this.limit) {
+							return testPos
+						}
+						*/
+					}
+					//console.log("charNum:", charNum);
+					if (charNum == -1) {
+						//console.groupEnd();
+						return this.text.getEndIndex(pos);
+					} else {
+						// When case there is new line between pos and charnum then use this new line
+						var newLineIndex = this.text.getEndIndex(pos);
+						if (newLineIndex < charNum ) {
+							console.log("newLineIndex <= charNum, newLineIndex:"+newLineIndex+", charNum:"+charNum, "\"" + this.text.text.substr(newLineIndex+1).replace(/\n/g, "?") + "\"");
+							//console.groupEnd();
+							
+							return newLineIndex;
+						}
+							
+						//var charAtMaxAdvance  = this.text.text.substring(charNum, charNum + 1);
+						var charAtMaxAdvance  = this.text.getCharAtPos(charNum);
+						//console.log("!!charAtMaxAdvance: " + charAtMaxAdvance);
+						//console.groupEnd();
+						return charNum;
+					}
+				}, 
+				getPosition: function() {
+					return this.pos;
+				}
+			};

+ 387 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/Polyline.js

@@ -0,0 +1,387 @@
+/**
+ * Class to generate polyline
+ *
+ * @author Dmitry Farafonov
+ */
+ 
+var ANCHOR_TYPE= {
+	main: "main",
+	middle: "middle",
+	first: "first",
+	last: "last"
+};
+
+function Anchor(uuid, type, x, y) {
+	this.uuid = uuid; 
+	this.x = x
+	this.y = y
+	this.type = (type == ANCHOR_TYPE.middle) ? ANCHOR_TYPE.middle : ANCHOR_TYPE.main;
+};
+Anchor.prototype = {
+	uuid: null,
+	x: 0,
+	y: 0,
+	type: ANCHOR_TYPE.main,
+	isFirst: false,
+	isLast: false,
+	ndex: 0,
+	typeIndex: 0
+};
+
+function Polyline(uuid, points, strokeWidth) {
+	/* Array on coordinates:
+	 * points: [{x: 410, y: 110}, 1
+	 *			{x: 570, y: 110}, 1 2
+	 *			{x: 620, y: 240},   2 3
+	 *			{x: 750, y: 270},     3 4
+	 *			{x: 650, y: 370}];      4
+	 */
+	this.points = points;
+	
+	/*
+	 * path for graph
+	 * [["M", x1, y1], ["L", x2, y2], ["C", ax, ay, bx, by, x3, y3], ["L", x3, y3]]
+	 */
+	this.path = [];
+	
+	this.anchors = [];
+	
+	if (strokeWidth) this.strokeWidth = strokeWidth;
+	
+	this.closePath = false;
+	
+	this.init();
+};
+
+Polyline.prototype = {
+	id: null,
+	points: [],
+	path: [],
+	anchors: [],
+	strokeWidth: 1,
+	radius: 15,
+	showDetails: false,
+	element: null,
+	isDefaultConditionAvailable: false,
+	closePath: false,
+	
+	init: function(points){
+		var linesCount = this.getLinesCount();
+		if (linesCount < 1)
+			return;
+			
+		this.normalizeCoordinates();
+		
+		// create anchors
+		
+		this.pushAnchor(ANCHOR_TYPE.first, this.getLine(0).x1, this.getLine(0).y1);
+		
+		for(var i = 1; i < linesCount; i++){
+			var line1 = this.getLine(i-1),
+				line2 = this.getLine(i);
+			
+			//this.pushAnchor(ANCHOR_TYPE.middle, line1.x1 + line1.x2-line1.x1, line1.y1 + line1.y2-line1.y1);
+			this.pushAnchor(ANCHOR_TYPE.main,  line1.x2, line1.y2);
+			//this.pushAnchor(ANCHOR_TYPE.middle,  line2.x1 + line2.x2-line2.x1, line2.y1 + line2.y2-line2.y1);
+		}
+		
+		this.pushAnchor(ANCHOR_TYPE.last, this.getLine(linesCount-1).x2, this.getLine(linesCount-1).y2);
+		
+		this.rebuildPath();
+	},
+	
+	normalizeCoordinates: function(){
+		for(var i=0; i < this.points.length; i++){
+			this.points[i].x = parseFloat(this.points[i].x);
+			this.points[i].y = parseFloat(this.points[i].y);
+		}
+	},
+	
+	getLinesCount: function(){
+		return this.points.length-1;
+	},
+	_getLine: function(i){
+		return {x1: this.points[i].x, y1: this.points[i].y, x2: this.points[i+1].x, y2: this.points[i+1].y};
+	},
+	getLine: function(i){
+		var line = this._getLine(i);
+		line.angle = this.getLineAngle(i) ;
+		return line;
+	},
+	getLineAngle: function(i){
+		var line = this._getLine(i);
+		return Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
+	},
+	getLineLengthX: function(i){
+		var line = this.getLine(i);
+		return (line.x2 - line.x1);
+	},
+	getLineLengthY: function(i){
+		var line = this.getLine(i);
+		return (line.y2 - line.y1);
+	},
+	getLineLength: function(i){
+		var line = this.getLine(i);
+		return Math.sqrt(Math.pow(this.getLineLengthX(i), 2) + Math.pow(this.getLineLengthY(i), 2));
+	},
+	
+	getAnchors: function(){
+		// âåðíóòü îòñîðòèðîâàííûé ìàññèâ
+		// ????
+		return this.anchors;
+	},
+	getAnchorsCount: function(type){
+		if (!type)
+			return this.anchors.length;
+		else {
+			var count = 0;
+			for(var i=0; i < this.getAnchorsCount(); i++){
+				var anchor = this.anchors[i];
+				if (anchor.getType() == type) {
+					count++;
+				}
+			}
+			return count;
+		}
+	},
+	
+	pushAnchor: function(type, x, y, index){
+		if (type == ANCHOR_TYPE.first) {
+			index = 0;
+			typeIndex = 0;
+		} else if (type == ANCHOR_TYPE.last) {
+			index = this.getAnchorsCount();
+			typeIndex = 0;
+		} else if (!index) {
+			index = this.anchors.length;
+		} else {
+			// ïåðåáðàòü anchors, ñäâèíóòü ïîçèöèþ äëÿ êàæäîãî, íà÷èíàÿ ñ index
+			//var anchor = this.getAnchor()
+			for(var i=0; i < this.getAnchorsCount(); i++){
+				var anchor = this.anchors[i];
+				if (anchor.index > index) {
+					anchor.index++;
+					anchor.typeIndex++;
+				}
+			}
+		}
+		
+		var anchor = new Anchor(this.id, ANCHOR_TYPE.main, x, y, index, typeIndex);
+		
+		this.anchors.push(anchor);
+	},
+	
+	getAnchor: function(position){
+		return this.anchors[position];
+	},
+	
+	getAnchorByType: function(type, position){
+		if (type == ANCHOR_TYPE.first)
+			return this.anchors[0];
+		if (type == ANCHOR_TYPE.last)
+			return this.anchors[this.getAnchorsCount()-1];
+		
+		for(var i=0; i < this.getAnchorsCount(); i++){
+			var anchor = this.anchors[i];
+			if (anchor.type == type) {
+				if( position == anchor.position)
+					return anchor;
+			}
+		}
+		return null;
+	},
+	
+	addNewPoint: function(position, x, y){
+		// 
+		for(var i = 0; i < this.getLinesCount(); i++){
+			var line = this.getLine(i);
+			if (x > line.x1 && x < line.x2 && y > line.y1 && y < line.y2) {
+				this.points.splice(i+1,0,{x: x, y: y});
+				break;
+			}
+		}
+		
+		this.rebuildPath();
+	},
+	
+	rebuildPath: function(){
+		var path = [];
+		
+		for(var i = 0; i < this.getAnchorsCount(); i++){
+			var anchor = this.getAnchor(i);
+			
+			var pathType = ""
+			if (i==0)
+				pathType = "M";
+			else 
+				pathType = "L";
+			
+// TODO: save previous points and calculate new path just if points are updated, and then save currents values as previous
+			
+			var targetX = anchor.x, targetY = anchor.y;
+			if (i>0 && i < this.getAnchorsCount()-1) {
+				// get new x,y
+				var cx = anchor.x, cy = anchor.y;
+				
+				// pivot point of prev line
+				var AO = this.getLineLength(i-1);
+				if (AO < this.radius) {
+					AO = this.radius;
+				}
+				
+				this.isDefaultConditionAvailable = (this.isDefaultConditionAvailable || (i == 1 && AO > 10));
+				//console.log("isDefaultConditionAvailable", this.isDefaultConditionAvailable);
+				
+				var ED = this.getLineLengthY(i-1) * this.radius / AO;
+				var OD = this.getLineLengthX(i-1) * this.radius / AO;
+					targetX = anchor.x - OD;
+					targetY = anchor.y - ED;
+				
+				if (AO < 2*this.radius && i>1) {
+					targetX = anchor.x - this.getLineLengthX(i-1)/2;
+					targetY = anchor.y - this.getLineLengthY(i-1)/2;;
+				}
+					
+				// pivot point of next line
+				var AO = this.getLineLength(i);
+				if (AO < this.radius) {
+					AO = this.radius;
+				}
+				var ED = this.getLineLengthY(i) * this.radius / AO;
+				var OD = this.getLineLengthX(i) * this.radius / AO;
+					var nextSrcX = anchor.x + OD;
+					var nextSrcY = anchor.y + ED;
+					
+				if (AO < 2*this.radius && i<this.getAnchorsCount()-2) {
+					nextSrcX = anchor.x + this.getLineLengthX(i)/2;
+					nextSrcY = anchor.y + this.getLineLengthY(i)/2;;
+				}
+					
+				
+				var dx0 = (cx - targetX) / 3,
+					dy0 = (cy - targetY) / 3,
+					ax = cx - dx0,
+					ay = cy - dy0,
+					
+					dx1 = (cx - nextSrcX) / 3,
+					dy1 = (cy - nextSrcY) / 3,
+					bx = cx - dx1,
+					by = cy - dy1,
+					
+					zx=nextSrcX, zy=nextSrcY;
+					
+				if (this.showDetails) {
+					var c = ProcessDiagramCanvas.g.path("M"+targetX+","+targetY+"L"+ax+","+ay).attr({stroke: Color.get(255, 153, 51), "stroke-dasharray": "- "});
+					var c = ProcessDiagramCanvas.g.path("M"+nextSrcX+","+nextSrcY+"L"+bx+","+by).attr({stroke: Color.get(255, 153, 51), "stroke-dasharray": "- "});
+					var c = ProcessDiagramCanvas.g.ellipse(ax, ay, 2, 2).attr({stroke: Color.SlateGrey});
+					var c = ProcessDiagramCanvas.g.ellipse(bx, by, 2, 2).attr({stroke: Color.SlateGrey});
+					var c = ProcessDiagramCanvas.g.ellipse(cx, cy, this.radius, this.radius).attr({stroke: Color.Gainsboro});
+					var c = ProcessDiagramCanvas.g.ellipse(targetX, targetY, 2, 2).attr({fill: Color.red});
+					var c = ProcessDiagramCanvas.g.ellipse(nextSrcX, nextSrcY, 2, 2).attr({fill: Color.red});
+				}
+			} else if (i==1 && this.getAnchorsCount() == 2){
+				var AO = this.getLineLength(i-1);
+				if (AO < this.radius) {
+					AO = this.radius;
+				}
+				this.isDefaultConditionAvailable = (this.isDefaultConditionAvailable || (i == 1 && AO > 10));
+				//console.log("-- isDefaultConditionAvailable", this.isDefaultConditionAvailable);
+			}
+
+			// anti smoothing
+			if (this.strokeWidth%2 == 1) {
+				targetX += 0.5;
+				targetY += 0.5;
+			}
+			
+			path.push([pathType, targetX, targetY]);
+			
+			if (i>0 && i < this.getAnchorsCount()-1) {
+				path.push(["C", ax, ay, bx, by, zx, zy]);
+			}
+		}
+		
+		if (this.closePath) {
+			console.log("closePath:", this.closePath);
+			path.push(["Z"]);
+		}
+		
+		this.path = path;
+	},
+	
+	transform: function(transformation){
+		this.element.transform(transformation);
+	},
+	attr: function(attrs){
+		//console.log("attrs: " +attrs, "", this.element);
+		// TODO: foreach and set each
+		this.element.attr(attrs);
+	}
+};
+
+function Polygone(points, strokeWidth) {
+	/* Array on coordinates:
+	 * points: [{x: 410, y: 110}, 1
+	 *			{x: 570, y: 110}, 1 2
+	 *			{x: 620, y: 240},   2 3
+	 *			{x: 750, y: 270},     3 4
+	 *			{x: 650, y: 370}];      4
+	 */
+	this.points = points;
+	
+	/*
+	 * path for graph
+	 * [["M", x1, y1], ["L", x2, y2], ["C", ax, ay, bx, by, x3, y3], ["L", x3, y3]]
+	 */
+	this.path = [];
+	
+	this.anchors = [];
+	
+	if (strokeWidth) this.strokeWidth = strokeWidth;
+	
+	this.closePath = true;
+	this.init();
+};
+
+
+/*
+ * Poligone is inherited from Poliline: draws closedPath of polyline
+ */
+
+var Foo = function () { };
+Foo.prototype = Polyline.prototype;
+
+Polygone.prototype = new Foo();
+
+Polygone.prototype.rebuildPath = function(){
+	var path = [];
+	//console.log("Polygone rebuildPath");
+	for(var i = 0; i < this.getAnchorsCount(); i++){
+		var anchor = this.getAnchor(i);
+		
+		var pathType = ""
+		if (i==0)
+			pathType = "M";
+		else 
+			pathType = "L";
+		
+		var targetX = anchor.x, targetY = anchor.y;
+		
+		// anti smoothing
+		if (this.strokeWidth%2 == 1) {
+			targetX += 0.5;
+			targetY += 0.5;
+		}
+		
+		path.push([pathType, targetX, targetY]);	
+	}
+	if (this.closePath)
+		path.push(["Z"]);
+	
+	this.path = path;
+};
+/*
+Polygone.prototype.transform = function(transformation){
+	this.element.transform(transformation);
+};
+*/

+ 2172 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/ProcessDiagramCanvas.js

@@ -0,0 +1,2172 @@
+/**
+ * Represents a canvas on which BPMN 2.0 constructs can be drawn.
+ * 
+ * Some of the icons used are licenced under a Creative Commons Attribution 2.5
+ * License, see http://www.famfamfam.com/lab/icons/silk/
+ * 
+ * @see ProcessDiagramGenerator
+ * @author (Java) Joram Barrez
+ * @author (Javascript) Dmitry Farafonov
+ */
+ 
+//Color.Cornsilk
+
+var ARROW_HEAD_SIMPLE = "simple";
+var ARROW_HEAD_EMPTY = "empty";
+var ARROW_HEAD_FILL = "FILL";
+var MULTILINE_VERTICAL_ALIGN_TOP = "top";
+var MULTILINE_VERTICAL_ALIGN_MIDDLE = "middle";
+var MULTILINE_VERTICAL_ALIGN_BOTTOM = "bottom";
+var MULTILINE_HORIZONTAL_ALIGN_LEFT = "start";
+var MULTILINE_HORIZONTAL_ALIGN_MIDDLE = "middle";
+var MULTILINE_HORIZONTAL_ALIGN_RIGHT = "end";
+
+// Predefined sized
+var TEXT_PADDING = 3;
+var ARROW_WIDTH = 4;
+var CONDITIONAL_INDICATOR_WIDTH = 16;
+var MARKER_WIDTH = 12;
+var ANNOTATION_TEXT_PADDING = 7;
+
+// Colors
+var TASK_COLOR = Color.OldLace; // original: Color.get(255, 255, 204);
+var TASK_STROKE_COLOR = Color.black; /*Color.SlateGrey; */
+//var EXPANDED_SUBPROCESS_ATTRS = Color.black; /*Color.SlateGrey; */
+var BOUNDARY_EVENT_COLOR = Color.white;
+var CONDITIONAL_INDICATOR_COLOR = Color.get(255, 255, 255);
+var HIGHLIGHT_COLOR = Color.Firebrick1;
+//var SEQUENCEFLOW_COLOR = Color.DimGrey;
+var SEQUENCEFLOW_COLOR = Color.black;
+
+var CATCHING_EVENT_COLOR = Color.black; /* Color.SlateGrey; */
+var START_EVENT_COLOR = Color.get(251,251,251);
+var START_EVENT_STROKE_COLOR = Color.black; /* Color.SlateGrey; */
+var END_EVENT_COLOR = Color.get(251,251,251);
+//var END_EVENT_STROKE_COLOR = Color.black;
+var NONE_END_EVENT_COLOR = Color.Firebrick4;
+var NONE_END_EVENT_STROKE_COLOR = Color.Firebrick4;
+var ERROR_END_EVENT_COLOR = Color.Firebrick;
+var ERROR_END_EVENT_STROKE_COLOR = Color.Firebrick;
+//var LABEL_COLOR = Color.get(112, 146, 190);
+var LABEL_COLOR = Color.get(72, 106, 150);
+
+// Fonts
+var NORMAL_FONT = {font: "10px Arial", opacity: 1, fill: Color.black};
+var LABEL_FONT = {font: "11px Arial", "font-style":"italic", opacity: 1, "fill": LABEL_COLOR};
+var LABEL_FONT_SMOOTH = {font: "10px Arial", "font-style":"italic", opacity: 1, "fill": LABEL_COLOR, stroke: LABEL_COLOR, "stroke-width":.4};
+var TASK_FONT = {font: "11px Arial", opacity: 1, fill: Color.black};
+var TASK_FONT_SMOOTH = {font: "11px Arial", opacity: 1, fill: Color.black, stroke: LABEL_COLOR, "stroke-width":.4};
+var POOL_LANE_FONT = {font: "11px Arial", opacity: 1, fill: Color.black};
+var EXPANDED_SUBPROCESS_FONT = {font: "11px Arial", opacity: 1, fill: Color.black};
+
+// Strokes
+var NORMAL_STROKE = 1;
+var SEQUENCEFLOW_STROKE = 1.5;
+var SEQUENCEFLOW_HIGHLIGHT_STROKE = 2;
+var THICK_TASK_BORDER_STROKE = 2.5;
+var GATEWAY_TYPE_STROKE = 3.2;
+var END_EVENT_STROKE = NORMAL_STROKE+2;
+var MULTI_INSTANCE_STROKE = 1.3;
+var EVENT_SUBPROCESS_ATTRS = 	{"stroke": Color.black, "stroke-width": NORMAL_STROKE, "stroke-dasharray": ". "};
+//var EXPANDED_SUBPROCESS_ATTRS = {"stroke": Color.black, "stroke-width": NORMAL_STROKE, "fill": Color.FloralWhite};
+var EXPANDED_SUBPROCESS_ATTRS = {"stroke": Color.black, "stroke-width": NORMAL_STROKE, "fill": Color.WhiteSmoke};
+var NON_INTERRUPTING_EVENT_STROKE = "- ";
+
+var TASK_CORNER_ROUND = 10;
+var EXPANDED_SUBPROCESS_CORNER_ROUND = 10;
+
+// icons
+var ICON_SIZE = 16;
+var ICON_PADDING = 4;
+var USERTASK_IMAGE = 		"images/deployer/user.png";
+var SCRIPTTASK_IMAGE = 		"images/deployer/script.png";
+var SERVICETASK_IMAGE = 	"images/deployer/service.png";
+var RECEIVETASK_IMAGE = 	"images/deployer/receive.png";
+var SENDTASK_IMAGE = 		"images/deployer/send.png";
+var MANUALTASK_IMAGE = 		"images/deployer/manual.png";
+var BUSINESS_RULE_TASK_IMAGE = "images/deployer/business_rule.png";
+var TIMER_IMAGE = 			"images/deployer/timer.png";
+var MESSAGE_CATCH_IMAGE = 	"images/deployer/message_catch.png";
+var MESSAGE_THROW_IMAGE = 	"images/deployer/message_throw.png";
+var ERROR_THROW_IMAGE = 	"images/deployer/error_throw.png";
+var ERROR_CATCH_IMAGE = 	"images/deployer/error_catch.png";
+var SIGNAL_CATCH_IMAGE = 	"images/deployer/signal_catch.png";
+var SIGNAL_THROW_IMAGE = 	"images/deployer/signal_throw.png";
+var MULTIPLE_CATCH_IMAGE = 	"images/deployer/multiple_catch.png";
+
+
+var ObjectType = {
+	ELLIPSE: "ellipse",
+	FLOW: "flow",
+	RECT: "rect",
+	RHOMBUS: "rhombus"
+};
+
+function OBJ(type){
+	this.c = null;
+	this.type = type;
+	this.nestedElements = [];
+};
+OBJ.prototype = {
+	
+};
+
+var CONNECTION_TYPE = {
+	SEQUENCE_FLOW: "sequence_flow",
+	MESSAGE_FLOW: "message_flow",
+	ASSOCIATION: "association"
+};
+
+var ProcessDiagramCanvas = function(){
+};
+ProcessDiagramCanvas.prototype = {
+// var DefaultProcessDiagramCanvas = {
+	canvasHolder: "holder",
+	canvasWidth: 0, 
+	canvasHeight: 0,
+	paint: Color.black,
+	strokeWidth: 0,
+	font: null,
+	fontSmoothing: null,
+	
+	g: null,
+	ninjaPaper: null,
+	
+	objects: [],
+	
+	processDefinitionId: null,
+	activity: null,
+	
+	frame: null,
+	
+	
+	debug: false,
+	
+	/**
+	* Creates an empty canvas with given width and height.
+	*/
+	init: function(width, height, processDefinitionId){
+		this.canvasWidth = width;
+		this.canvasHeight = height;
+		
+		// TODO: name it as 'canvasName'
+		if (!processDefinitionId)
+			processDefinitionId = "holder";
+		
+		this.processDefinitionId = processDefinitionId;
+		this.canvasHolder = this.processDefinitionId;
+
+		var h = document.getElementById(this.canvasHolder);
+		if (!h) return;
+		
+		h.style.width = this.canvasWidth;
+		h.style.height = this.canvasHeight;
+		
+		this.g = Raphael(this.canvasHolder);
+		this.g.clear();
+	
+		//this.setPaint(Color.DimGrey);
+		this.setPaint(Color.black);
+		//this.setPaint(Color.white);
+		this.setStroke(NORMAL_STROKE);
+		
+		//this.setFont("Arial", 11);
+		this.setFont(NORMAL_FONT);
+		//this.font = this.g.getFont("Arial");
+		
+		this.fontSmoothing = true;
+		
+		// ninja!
+		var RaphaelOriginal = Raphael;
+		this.ninjaPaper =(function (local_raphael) {
+			var paper = local_raphael(1, 1, 1, 1, processDefinitionId);
+			return paper;
+		})(Raphael.ninja());
+		Raphael = RaphaelOriginal;
+	},
+	setPaint: function(color){
+		this.paint = color;
+	},
+	getPaint: function(){
+		return this.paint;
+	},
+	setStroke: function(strokeWidth){
+		this.strokeWidth = strokeWidth;
+	},
+	getStroke: function(){
+		return this.strokeWidth;
+	},
+	/*
+	setFont: function(family, weight, style, stretch){
+		this.font = this.g.getFont(family, weight);
+	},
+	*/
+	setFont: function(font){
+		this.font = font;
+	},
+	getFont: function(){
+		return this.font;
+	},
+	drawShaddow: function(object){
+		var border = object.clone();
+		border.attr({"stroke-width": this.strokeWidth + 6, 
+					"stroke": Color.white,
+					"fill": Color.white,
+					"opacity": 1,
+					"stroke-dasharray":null});
+		//border.toBack();
+		object.toFront();
+		
+		return border;
+	},
+	
+	setConextObject: function(obj){
+		this.contextObject = obj;
+	},
+	getConextObject: function(){
+		return this.contextObject;
+	},
+	setContextToElement: function(object){
+		var contextObject = this.getConextObject();
+		object.id = contextObject.id;
+		object.data("contextObject", contextObject);
+	},
+	onClick: function(event, instance, element){
+	  var overlay = element;
+	  var set = overlay.data("set");
+	  var contextObject = overlay.data("contextObject");
+	  //console.log("["+contextObject.getProperty("type")+"], activityId: " + contextObject.getId());
+	  if (ProcessDiagramGenerator.options && ProcessDiagramGenerator.options.on && ProcessDiagramGenerator.options.on.click) {
+	    var args = [instance, element, contextObject];
+	    ProcessDiagramGenerator.options.on.click.apply(event, args);
+	  }
+	},
+	onRightClick: function(event, instance, element){
+	  var overlay = element;
+	  var set = overlay.data("set");
+	  var contextObject = overlay.data("contextObject");
+	  //console.log("[%s], activityId: %s (RIGHTCLICK)", contextObject.getProperty("type"), contextObject.getId());
+
+	  if (ProcessDiagramGenerator.options && ProcessDiagramGenerator.options.on && ProcessDiagramGenerator.options.on.rightClick) {
+	    var args = [instance, element, contextObject];
+	    ProcessDiagramGenerator.options.on.rightClick.apply(event, args);
+	  }
+	},
+	onHoverIn: function(event, instance, element){
+	  var overlay = element;
+	  var set = overlay.data("set");
+	  var contextObject = overlay.data("contextObject");
+
+	  var border = instance.g.getById(contextObject.id + "_border");
+	  border.attr("opacity", 0.3);
+
+	  // provide callback
+	  if (ProcessDiagramGenerator.options && ProcessDiagramGenerator.options.on && ProcessDiagramGenerator.options.on.over) {
+	    var args = [instance, element, contextObject];
+	    ProcessDiagramGenerator.options.on.over.apply(event, args);
+	  }
+	 },
+	 onHoverOut: function(event, instance, element){
+	   var overlay = element;
+	   var set = overlay.data("set");
+	   var contextObject = overlay.data("contextObject");
+
+	   var border = instance.g.getById(contextObject.id + "_border");
+	   border.attr("opacity", 0.0);
+	   // provide callback
+	   if (ProcessDiagramGenerator.options && ProcessDiagramGenerator.options.on && ProcessDiagramGenerator.options.on.out) {
+	     var args = [instance, element, contextObject];
+	     ProcessDiagramGenerator.options.on.out.apply(event, args);
+	   }
+	 },
+	 addHandlers: function(set, x, y, width, height, type){
+	   var contextObject = this.getConextObject();
+
+	   var cx = x+width/2, cy = y+height/2;
+	   if (type == "event") {
+	     var border = this.g.ellipse(cx, cy, width/2+4, height/2+4);
+	     var overlay = this.g.ellipse(cx, cy, width/2, height/2);
+	   } else if (type == "gateway") {
+	     // rhombus
+	     var border = this.g.path( "M" + (x - 4) + " " + (y + (height / 2)) +
+	         "L" + (x + (width / 2)) + " " + (y + height + 4) +
+	         "L" + (x + width + 4) + " " + (y + (height / 2)) +
+	         "L" + (x + (width / 2)) + " " + (y - 4) +
+	         "z" );
+	     var overlay = this.g.path(  "M" + x + " " + (y + (height / 2)) +
+	         "L" + (x + (width / 2)) + " " + (y + height) +
+	         "L" + (x + width) + " " + (y + (height / 2)) +
+	         "L" + (x + (width / 2)) + " " + y +
+	         "z" );
+	   } else if (type == "task") {
+	     var border = this.g.rect(x - 4, y - 4, width+9, height+9, TASK_CORNER_ROUND+4);
+	     var overlay = this.g.rect(x, y, width, height, TASK_CORNER_ROUND);
+	   }
+
+	   border.attr({stroke: Color.get(132,112,255)/*Color.Tan1*/,"stroke-width": 4, opacity: 0.0});
+	   border.id = contextObject.id + "_border";
+
+	   set.push(border);
+
+	   overlay.attr({stroke: Color.Orange,"stroke-width": 3, fill: Color.get(0,0,0), opacity: 0.0, cursor: "hand"});
+	   overlay.data("set",set);
+	   overlay.id = contextObject.id;
+	   overlay.data("contextObject",contextObject);
+
+	   var instance = this;
+	   overlay.mousedown(function(event){if (event.button == 2) instance.onRightClick(event, instance, this);});
+	   overlay.click(function(event){instance.onClick(event, instance, this);});
+	   overlay.hover(function(event){instance.onHoverIn(event, instance, this);}, function(event){instance.onHoverOut(event, instance, this);});
+	 },
+	
+	/*
+	 * Start Events:
+	 * 
+	 *	drawNoneStartEvent
+	 *	drawTimerStartEvent
+	 *	drawMessageStartEvent
+	 *	drawErrorStartEvent
+	 *	drawSignalStartEvent
+	 *	_drawStartEventImage
+	 *	_drawStartEvent
+	 */
+	 
+	drawNoneStartEvent: function(x, y, width, height) {
+	  this.g.setStart();
+	  
+		var isInterrupting = undefined;
+		this._drawStartEvent(x, y, width, height, isInterrupting, null);
+		
+		var set = this.g.setFinish();
+		this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawTimerStartEvent: function(x, y, width, height, isInterrupting, name) {
+	  this.g.setStart();
+	  
+		this._drawStartEvent(x, y, width, height, isInterrupting, null);
+		
+		var cx = x + width/2 - this.getStroke()/4;
+		var cy = y + height/2 - this.getStroke()/4;
+		
+		var w = width*.9;// - this.getStroke()*2;
+		var h = height*.9;// - this.getStroke()*2;
+		
+		this._drawClock(cx, cy, w, h);
+		
+		if (this.gebug)
+			var center = this.g.ellipse(cx, cy, 3, 3).attr({stroke:"none", fill: Color.green});
+		
+		var set = this.g.setFinish();
+		this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawMessageStartEvent: function(x, y, width, height, isInterrupting, name) {
+	  this.g.setStart();
+	  
+		this._drawStartEvent(x, y, width, height, isInterrupting, null);
+		
+		this._drawStartEventImage(x, y, width, height, MESSAGE_CATCH_IMAGE);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawErrorStartEvent: function(x, y, width, height, name) {
+	  this.g.setStart();
+		var isInterrupting = undefined;
+		this._drawStartEvent(x, y, width, height, isInterrupting);
+
+		this._drawStartEventImage(x, y, width, height, ERROR_CATCH_IMAGE);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawSignalStartEvent: function(x, y, width, height, isInterrupting, name) {
+	  this.g.setStart();
+		this._drawStartEvent(x, y, width, height, isInterrupting, null);
+		
+		this._drawStartEventImage(x, y, width, height, SIGNAL_CATCH_IMAGE);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawMultipleStartEvent: function(x, y, width, height, isInterrupting, name) {
+	  this.g.setStart();
+		
+	  this._drawStartEvent(x, y, width, height, isInterrupting, null);
+		
+		var cx = x + width/2 - this.getStroke()/4;
+		var cy = y + height/2 - this.getStroke()/4;
+		
+		var w = width*1;
+		var h = height*1;
+		
+		this._drawPentagon(cx, cy, w, h);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	_drawStartEventImage: function(x, y, width, height, image){
+		var cx = x + width/2 - this.getStroke()/2;
+		var cy = y + height/2 - this.getStroke()/2;
+		
+		var w = width*.65;// - this.getStroke()*2;
+		var h = height*.65;// - this.getStroke()*2;
+		
+		var img = this.g.image(image, cx-w/2, cy-h/2, w, h);
+	},
+	_drawStartEvent: function(x, y, width, height, isInterrupting){
+		var originalPaint = this.getPaint();
+		if (typeof(START_EVENT_STROKE_COLOR) != "undefined")
+			this.setPaint(START_EVENT_STROKE_COLOR);
+		
+		
+		width -= this.strokeWidth / 2;
+		height -= this.strokeWidth / 2;
+		
+		x = x + width/2;
+		y = y + height/2;
+		
+		var circle = this.g.ellipse(x, y, width/2, height/2);
+		
+		circle.attr({"stroke-width": this.strokeWidth, 
+				"stroke": this.paint, 
+				//"stroke": START_EVENT_STROKE_COLOR,
+				"fill": START_EVENT_COLOR});
+				
+		// white shaddow
+		this.drawShaddow(circle);
+		
+		if (isInterrupting!=null && isInterrupting!=undefined && !isInterrupting) 
+			circle.attr({"stroke-dasharray": NON_INTERRUPTING_EVENT_STROKE});
+
+		this.setContextToElement(circle);
+		
+		
+		this.setPaint(originalPaint);
+	},
+	
+	/*
+	 * End Events:
+	 * 
+	 *	drawNoneEndEvent
+	 *	drawErrorEndEvent
+	 *	drawMessageEndEvent
+	 *	drawSignalEndEvent
+	 *	drawMultipleEndEvent
+	 *  _drawEndEventImage
+	 *	_drawNoneEndEvent
+	 */
+	 
+	drawNoneEndEvent: function(x, y, width, height) {
+	  this.g.setStart();
+	  
+		this._drawNoneEndEvent(x, y, width, height, null, "noneEndEvent");
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawErrorEndEvent: function(x, y, width, height) {
+	  this.g.setStart();
+		var type = "errorEndEvent";
+		this._drawNoneEndEvent(x, y, width, height, null, type);
+		
+		this._drawEndEventImage(x, y, width, height, ERROR_THROW_IMAGE);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawMessageEndEvent: function(x, y, width, height, name) {
+	  this.g.setStart();
+		var type = "errorEndEvent";
+		this._drawNoneEndEvent(x, y, width, height, null, type);
+		
+		this._drawEndEventImage(x, y, width, height, MESSAGE_THROW_IMAGE);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawSignalEndEvent: function(x, y, width, height, name) {
+	  this.g.setStart();
+		var type = "errorEndEvent";
+		this._drawNoneEndEvent(x, y, width, height, null, type);
+		
+		this._drawEndEventImage(x, y, width, height, SIGNAL_THROW_IMAGE);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawMultipleEndEvent: function(x, y, width, height, name) {
+	  this.g.setStart();
+		var type = "errorEndEvent";
+		this._drawNoneEndEvent(x, y, width, height, null, type);
+		
+		var cx = x + width/2;// - this.getStroke();
+		var cy = y + height/2;// - this.getStroke();
+		
+		var w = width*1;
+		var h = height*1;
+		
+		var filled = true;
+		this._drawPentagon(cx, cy, w, h, filled);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawTerminateEndEvent: function(x, y, width, height) {
+	  this.g.setStart();
+		var type = "errorEndEvent";
+		this._drawNoneEndEvent(x, y, width, height, null, type);
+		
+		var cx = x + width/2;// - this.getStroke()/2;
+		var cy = y + height/2;// - this.getStroke()/2;
+		
+		var w = width/2*.6;
+		var h = height/2*.6;
+		
+		var circle = this.g.ellipse(cx, cy, w, h).attr({fill: Color.black});
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	_drawEndEventImage: function(x, y, width, height, image){
+		var cx = x + width/2 - this.getStroke()/2;
+		var cy = y + height/2 - this.getStroke()/2;
+		
+		var w = width*.65;
+		var h = height*.65;
+		
+		var img = this.g.image(image, cx-w/2, cy-h/2, w, h);
+	},
+	
+	_drawNoneEndEvent: function(x, y, width, height, image, type) {
+		var originalPaint = this.getPaint();
+		if (typeof(CATCHING_EVENT_COLOR) != "undefined")
+			this.setPaint(CATCHING_EVENT_COLOR);
+			
+		var strokeColor = this.getPaint();
+		var fillColor = this.getPaint();
+		
+		if (type == "errorEndEvent") {
+			strokeColor = ERROR_END_EVENT_STROKE_COLOR;
+			fillColor = ERROR_END_EVENT_COLOR;
+		} else if (type == "noneEndEvent") {
+			strokeColor = NONE_END_EVENT_STROKE_COLOR;
+			fillColor = NONE_END_EVENT_COLOR;
+		} else 
+			
+		// event circles
+		width -= this.strokeWidth / 2;
+		height -= this.strokeWidth / 2;
+		
+		x = x + width/2;// + this.strokeWidth/2;
+		y = y + width/2;// + this.strokeWidth/2;
+		
+		// outerCircle
+		var outerCircle = this.g.ellipse(x, y, width/2, height/2);
+		
+		// white shaddow
+		var shaddow = this.drawShaddow(outerCircle);
+		
+		outerCircle.attr({"stroke-width": this.strokeWidth,
+						"stroke": strokeColor,
+						"fill": fillColor});
+		
+		var innerCircleX = x;
+		var innerCircleY = y;
+		var innerCircleWidth = width/2 - 2;
+		var innerCircleHeight = height/2 - 2;
+		var innerCircle = this.g.ellipse(innerCircleX, innerCircleY, innerCircleWidth, innerCircleHeight);
+		innerCircle.attr({"stroke-width": this.strokeWidth,
+				"stroke": strokeColor,
+				"fill": Color.white});
+
+		// TODO: implement it
+		//var originalPaint = this.getPaint();
+		//this.g.setPaint(BOUNDARY_EVENT_COLOR);
+		
+		this.setPaint(originalPaint);
+	},
+	
+	/*
+	 * Catching Events:
+	 * 
+	 *	drawCatchingTimerEvent
+	 *	drawCatchingErrorEvent
+	 *	drawCatchingSignalEvent
+	 *  drawCatchingMessageEvent
+	 *	drawCatchingMultipleEvent
+	 *	_drawCatchingEventImage
+	 *	_drawCatchingEvent
+	 */
+	 
+	
+	drawCatchingTimerEvent: function(x, y, width, height, isInterrupting, name) {
+	  this.g.setStart();
+		this._drawCatchingEvent(x, y, width, height, isInterrupting, null);
+		
+		var innerCircleWidth = width - 4;
+		var innerCircleHeight = height - 4;
+		
+		var cx = x + width/2 - this.getStroke()/4;
+		var cy = y + height/2 - this.getStroke()/4;
+		
+		var w = innerCircleWidth*.9;// - this.getStroke()*2;
+		var h = innerCircleHeight*.9;// - this.getStroke()*2;
+		
+		this._drawClock(cx, cy, w, h);
+		
+		var set = this.g.setFinish();
+		this.addHandlers(set, x, y, width, height, "event");
+	},
+
+	drawCatchingErrorEvent: function(x, y, width, height, isInterrupting, name) {
+	  this.g.setStart();
+		this._drawCatchingEvent(x, y, width, height, isInterrupting, null);
+		
+		this._drawCatchingEventImage(x, y, width, height, ERROR_CATCH_IMAGE);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawCatchingSignalEvent: function(x, y, width, height, isInterrupting, name) {
+	  this.g.setStart();
+		this._drawCatchingEvent(x, y, width, height, isInterrupting, null);
+		
+		this._drawCatchingEventImage(x, y, width, height, SIGNAL_CATCH_IMAGE);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawCatchingMessageEvent: function(x, y, width, height, isInterrupting, name) {
+	  this.g.setStart();
+		this._drawCatchingEvent(x, y, width, height, isInterrupting, null);
+		
+		this._drawCatchingEventImage(x, y, width, height, MESSAGE_CATCH_IMAGE);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawCatchingMultipleEvent: function(x, y, width, height, isInterrupting, name) {
+	  this.g.setStart();
+		this._drawCatchingEvent(x, y, width, height, isInterrupting, null);
+		
+		var cx = x + width/2 - this.getStroke();
+		var cy = y + height/2 - this.getStroke();
+		
+		var w = width*.9;
+		var h = height*.9;
+		
+		this._drawPentagon(cx, cy, w, h);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	_drawCatchingEventImage: function(x, y, width, height, image){
+		var innerCircleWidth = width - 4;
+		var innerCircleHeight = height - 4;
+		
+		var cx = x + width/2 - this.getStroke()/2;
+		var cy = y + height/2 - this.getStroke()/2;
+		
+		var w = innerCircleWidth*.6;// - this.getStroke()*2;
+		var h = innerCircleHeight*.6;// - this.getStroke()*2;
+		
+		var img = this.g.image(image, cx-w/2, cy-h/2, w, h);
+	},
+	
+	_drawCatchingEvent: function(x, y, width, height, isInterrupting, image) {
+		var originalPaint = this.getPaint();
+		if (typeof(CATCHING_EVENT_COLOR) != "undefined")
+			this.setPaint(CATCHING_EVENT_COLOR);
+			
+		// event circles
+		width -= this.strokeWidth / 2;
+		height -= this.strokeWidth / 2;
+		
+		x = x + width/2;// + this.strokeWidth/2;
+		y = y + width/2;// + this.strokeWidth/2;
+		
+		// outerCircle
+		var outerCircle = this.g.ellipse(x, y, width/2, height/2);
+		
+		// white shaddow
+		var shaddow = this.drawShaddow(outerCircle);
+		
+		//console.log("isInterrupting: " + isInterrupting, "x:" , x, "y:",y);
+		if (isInterrupting!=null && isInterrupting!=undefined && !isInterrupting) 
+			outerCircle.attr({"stroke-dasharray": NON_INTERRUPTING_EVENT_STROKE});
+		
+		outerCircle.attr({"stroke-width": this.strokeWidth,
+						"stroke": this.getPaint(),
+						"fill": BOUNDARY_EVENT_COLOR});
+		
+		var innerCircleX = x;
+		var innerCircleY = y;
+		var innerCircleRadiusX = width/2 - 4;
+		var innerCircleRadiusY = height/2 - 4;
+		var innerCircle = this.g.ellipse(innerCircleX, innerCircleY, innerCircleRadiusX, innerCircleRadiusY);
+		innerCircle.attr({"stroke-width": this.strokeWidth,
+				"stroke": this.getPaint()});
+
+		if (image) {
+			var imageWidth = imageHeight = innerCircleRadiusX*1.2 + this.getStroke()*2;
+			var imageX = innerCircleX-imageWidth/2 - this.strokeWidth/2;
+			var imageY = innerCircleY-imageWidth/2 - this.strokeWidth/2;
+			var img = this.g.image(image, imageX, imageY, imageWidth, imageHeight);
+		}
+		
+		this.setPaint(originalPaint);
+		
+		var set = this.g.set();
+		set.push(outerCircle, innerCircle, shaddow);
+		this.setContextToElement(outerCircle);
+		
+		// TODO: add shapes to set
+		
+		/*
+		var st = this.g.set();
+		st.push(
+			this.g.ellipse(innerCircleX, innerCircleY, 2, 2),
+			this.g.ellipse(imageX, imageY, 2, 2)
+		);
+		st.attr({fill: "red", "stroke-width":0});
+		*/
+	},
+	
+	/*
+	 * Catching Events:
+	 * 
+	 *	drawThrowingNoneEvent
+	 *	drawThrowingSignalEvent
+	 *	drawThrowingMessageEvent
+	 *	drawThrowingMultipleEvent
+	 */
+	
+	drawThrowingNoneEvent: function(x, y, width, height, name) {
+	  this.g.setStart();
+		this._drawCatchingEvent(x, y, width, height, null, null);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawThrowingSignalEvent: function(x, y, width, height, name) {
+	  this.g.setStart();
+		this._drawCatchingEvent(x, y, width, height, null, null);
+		
+		this._drawCatchingEventImage(x, y, width, height, SIGNAL_THROW_IMAGE);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawThrowingMessageEvent: function(x, y, width, height, name) {
+	  this.g.setStart();
+		this._drawCatchingEvent(x, y, width, height, null, null);
+		
+		this._drawCatchingEventImage(x, y, width, height, MESSAGE_THROW_IMAGE);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	drawThrowingMultipleEvent: function(x, y, width, height, name) {
+	  this.g.setStart();
+		this._drawCatchingEvent(x, y, width, height, null, null);
+		
+		var cx = x + width/2 - this.getStroke();
+		var cy = y + height/2 - this.getStroke();
+		
+		var w = width*.9;
+		var h = height*.9;
+		
+		var filled = true;
+		this._drawPentagon(cx, cy, w, h, filled);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "event");
+	},
+	
+	/*
+	 * Draw flows:
+	 * 
+	 *  _connectFlowToActivity
+	 *	_drawFlow
+	 *	_drawDefaultSequenceFlowIndicator
+	 *	drawSequenceflow
+	 *	drawMessageflow
+	 *	drawAssociation
+	 *	_drawCircleTail
+	 *	_drawArrowHead
+	 *	_drawConditionalSequenceFlowIndicator
+	 *	drawSequenceflowWithoutArrow
+	 */
+	 
+	_connectFlowToActivity: function(sourceActivityId, destinationActivityId, waypoints){
+		var sourceActivity = this.g.getById(sourceActivityId);
+		var destinationActivity = this.g.getById(destinationActivityId);
+		if (sourceActivity == null || destinationActivity == null) {
+			if (sourceActivity == null)
+				console.error("source activity["+sourceActivityId+"] not found");
+			else
+				console.error("destination activity["+destinationActivityId+"] not found");
+			return null;
+		}
+			var bbSourceActivity = sourceActivity.getBBox()
+			var bbDestinationActivity = destinationActivity.getBBox()
+			
+			var path = [];
+			var newWaypoints = [];
+			for(var i = 0; i < waypoints.length; i++){
+				var pathType = ""
+				if (i==0)
+					pathType = "M";
+				else 
+					pathType = "L";
+					
+				path.push([pathType, waypoints[i].x, waypoints[i].y]);
+				newWaypoints.push({x:waypoints[i].x, y:waypoints[i].y});
+			}
+
+			var ninjaPathSourceActivity = this.ninjaPaper.path(sourceActivity.realPath);
+			var ninjaPathDestinationActivity = this.ninjaPaper.path(destinationActivity.realPath);
+			var ninjaBBSourceActivity = ninjaPathSourceActivity.getBBox();
+			var ninjaBBDestinationActivity = ninjaPathDestinationActivity.getBBox();
+			
+			// set target of the flow to the center of the taskObject
+			var newPath = path;
+			var originalSource = {x: newPath[0][1], y: newPath[0][2]};
+			var originalTarget = {x: newPath[newPath.length-1][1], y: newPath[newPath.length-1][2]};
+			newPath[0][1] = ninjaBBSourceActivity.x + (ninjaBBSourceActivity.x2 - ninjaBBSourceActivity.x ) / 2;
+			newPath[0][2] = ninjaBBSourceActivity.y + (ninjaBBSourceActivity.y2 - ninjaBBSourceActivity.y ) / 2;
+			newPath[newPath.length-1][1] = ninjaBBDestinationActivity.x + (ninjaBBDestinationActivity.x2 - ninjaBBDestinationActivity.x ) / 2;
+			newPath[newPath.length-1][2] = ninjaBBDestinationActivity.y + (ninjaBBDestinationActivity.y2 - ninjaBBDestinationActivity.y ) / 2;
+			
+			var ninjaPathFlowObject = this.ninjaPaper.path(newPath);
+			var ninjaBBFlowObject = ninjaPathFlowObject.getBBox();
+			
+			var intersectionsSource = Raphael.pathIntersection(ninjaPathSourceActivity.realPath, ninjaPathFlowObject.realPath);
+			var intersectionsDestination = Raphael.pathIntersection(ninjaPathDestinationActivity.realPath, ninjaPathFlowObject.realPath);
+			var intersectionSource = intersectionsSource.pop();
+			var intersectionDestination = intersectionsDestination.pop();
+			
+			if (intersectionSource != undefined) {
+				if (this.gebug) {
+					var diameter = 5;
+					var dotOriginal = this.g.ellipse(originalSource.x, originalSource.y, diameter, diameter).attr({"fill": Color.white, "stroke": Color.Pink});
+					var dot = this.g.ellipse(intersectionSource.x, intersectionSource.y, diameter, diameter).attr({"fill": Color.white, "stroke": Color.Green});
+				}
+				
+				newWaypoints[0].x = intersectionSource.x;
+				newWaypoints[0].y = intersectionSource.y;
+			}
+			if (intersectionDestination != undefined) {
+				if (this.gebug) {
+					var diameter = 5;
+					var dotOriginal = this.g.ellipse(originalTarget.x, originalTarget.y, diameter, diameter).attr({"fill": Color.white, "stroke": Color.Red});
+					var dot = this.g.ellipse(intersectionDestination.x, intersectionDestination.y, diameter, diameter).attr({"fill": Color.white, "stroke": Color.Blue});
+				}
+				
+				newWaypoints[newWaypoints.length-1].x = intersectionDestination.x;
+				newWaypoints[newWaypoints.length-1].y = intersectionDestination.y;
+			}
+			
+			this.ninjaPaper.clear();
+		return newWaypoints;
+	},
+	 
+	_drawFlow: function(waypoints, conditional, isDefault, highLighted, withArrowHead, connectionType){
+		var originalPaint = this.getPaint();
+		var originalStroke = this.getStroke();
+		
+		this.setPaint(SEQUENCEFLOW_COLOR);
+		this.setStroke(SEQUENCEFLOW_STROKE);
+		
+		if (highLighted) {
+			this.setPaint(HIGHLIGHT_COLOR);
+			this.setStroke(SEQUENCEFLOW_HIGHLIGHT_STROKE);
+		}
+
+// TODO: generate polylineId or do something!!
+		var uuid = Raphael.createUUID();
+		
+		var contextObject = this.getConextObject();
+		var newWaypoints = waypoints;
+		if (contextObject) {
+			var newWaypoints = this._connectFlowToActivity(contextObject.sourceActivityId, contextObject.destinationActivityId, waypoints);
+			
+			if (!newWaypoints) {
+				console.error("Error draw flow from '"+contextObject.sourceActivityId+"' to '"+contextObject.destinationActivityId+"' ");
+				return;
+			}
+		}
+		var polyline = new Polyline(uuid, newWaypoints, this.getStroke());
+		//var polyline = new Polyline(waypoints, 3);
+		
+		polyline.element = this.g.path(polyline.path);
+		polyline.element.attr("stroke-width", this.getStroke());
+		polyline.element.attr("stroke", this.getPaint());
+			
+		if (contextObject) {
+			polyline.element.id = contextObject.id;
+			polyline.element.data("contextObject", contextObject);
+		} else {
+			polyline.element.id = uuid;
+		}
+		
+		
+		/*
+		polyline.element.mouseover(function(){
+			this.attr({"stroke-width": NORMAL_STROKE + 2});
+		}).mouseout(function(){
+			this.attr({"stroke-width": NORMAL_STROKE});
+		});
+		*/
+		
+		var last = polyline.getAnchorsCount()-1;
+		var x = polyline.getAnchor(last).x;
+		var y = polyline.getAnchor(last).y;
+		//var c = this.g.ellipse(x, y, 5, 5);
+		
+		var lastLineIndex = polyline.getLinesCount()-1;
+		var line = polyline.getLine(lastLineIndex);
+		var firstLine = polyline.getLine(0);
+		
+		var arrowHead = null,
+			circleTail = null,
+			defaultSequenceFlowIndicator = null,
+			conditionalSequenceFlowIndicator = null;
+
+		if (connectionType == CONNECTION_TYPE.MESSAGE_FLOW) {
+			circleTail = this._drawCircleTail(firstLine, connectionType);
+		}
+		if(withArrowHead)
+			arrowHead = this._drawArrowHead(line, connectionType);
+		
+		//console.log("isDefault: ", isDefault, ", isDefaultConditionAvailable: ", polyline.isDefaultConditionAvailable);
+		if (isDefault && polyline.isDefaultConditionAvailable) {
+			//var angle = polyline.getLineAngle(0);
+			//console.log("firstLine", firstLine);
+			defaultSequenceFlowIndicator = this._drawDefaultSequenceFlowIndicator(firstLine);
+		}
+		
+		if (conditional) {
+			conditionalSequenceFlowIndicator = this._drawConditionalSequenceFlowIndicator(firstLine);
+		}
+
+        // draw flow name
+        var flowName = contextObject.name;
+        if (flowName) {
+            var xPointArray = contextObject.xPointArray;
+            var yPointArray = contextObject.yPointArray;
+            var textX = xPointArray[0] < xPointArray[1] ? xPointArray[0] : xPointArray[1];
+            var textY = yPointArray[0] < yPointArray[1] ? yPointArray[1] : yPointArray[0];
+            // fix xy
+            textX += 20;
+            textY -= 10;
+            this.g.text(textX, textY, flowName).attr(LABEL_FONT);
+        }
+		
+		var st = this.g.set();
+		st.push(polyline.element, arrowHead, circleTail, conditionalSequenceFlowIndicator);
+		polyline.element.data("set", st);
+		polyline.element.data("withArrowHead", withArrowHead);
+		
+		var polyCloneAttrNormal = {"stroke-width": this.getStroke() + 5, stroke: Color.get(132,112,255), opacity: 0.0, cursor: "hand"};
+		var polyClone = st.clone().attr(polyCloneAttrNormal).hover(function () {
+				//if (polyLine.data("isSelected")) return;
+				polyClone.attr({opacity: 0.2});
+			}, function () {
+				//if (polyLine.data("isSelected")) return;
+				polyClone.attr({opacity: 0.0});
+			});
+		polyClone.data("objectId", polyline.element.id);
+		polyClone.click(function(){
+			var instance = this;
+			var objectId = instance.data("objectId");
+			var object = this.paper.getById(objectId);
+			var contextObject = object.data("contextObject");
+			if (contextObject) {
+				console.log("[flow], objectId: " + object.id +", flow: " + contextObject.flow);
+				ProcessDiagramGenerator.showFlowInfo(contextObject);
+			}
+		}).dblclick(function(){
+			console.log("!!! DOUBLE CLICK !!!");
+		}).hover(function (mouseEvent) {
+			var instance = this;
+			var objectId = instance.data("objectId");
+			var object = this.paper.getById(objectId);
+			var contextObject = object.data("contextObject");
+			if (contextObject)
+				ProcessDiagramGenerator.showFlowInfo(contextObject);
+		});
+		polyClone.data("parentId", uuid);
+		
+		if (!connectionType || connectionType == CONNECTION_TYPE.SEQUENCE_FLOW)
+			polyline.element.attr("stroke-width", this.getStroke());
+		else if (connectionType == CONNECTION_TYPE.MESSAGE_FLOW)
+			polyline.element.attr({"stroke-dasharray": "--"});
+		else if (connectionType == CONNECTION_TYPE.ASSOCIATION)
+			polyline.element.attr({"stroke-dasharray": ". "});
+		
+		this.setPaint(originalPaint);
+		this.setStroke(originalStroke);
+	},
+	
+	_drawDefaultSequenceFlowIndicator: function(line) {
+		//console.log("line: ", line);
+		
+		var len = 10; c = len/2, f = 8;
+		var defaultIndicator = this.g.path("M" + (-c) + " " + 0 + "L" + (c) + " " + 0);
+		defaultIndicator.attr("stroke-width", this.getStroke()+0);
+		defaultIndicator.attr("stroke", this.getPaint());
+		
+		
+		var cosAngle = Math.cos((line.angle));
+		var sinAngle = Math.sin((line.angle));
+		
+		var dx = f * cosAngle;
+		var dy = f * sinAngle;
+		
+		var x1 = line.x1 + dx + 0*c*cosAngle;
+		var y1 = line.y1 + dy + 0*c*sinAngle;
+		
+		defaultIndicator.transform("t" + (x1) + "," + (y1) + "");
+		defaultIndicator.transform("...r" + Raphael.deg(line.angle - 3*Math.PI / 4) + " " + 0 + " " + 0);
+		/*
+		var c0 = this.g.ellipse(0, 0, 1, 1).attr({stroke: Color.Blue});
+		c0.transform("t" + (line.x1) + "," + (line.y1) + "");
+		var center = this.g.ellipse(0, 0, 1, 1).attr({stroke: Color.Red});
+		center.transform("t" + (line.x1+dx) + "," + (line.y1+dy) + "");
+		*/
+		
+		return defaultIndicator;
+	},
+	
+	drawSequenceflow: function(waypoints, conditional, isDefault, highLighted) {
+		var withArrowHead = true;
+		this._drawFlow(waypoints, conditional, isDefault, highLighted, withArrowHead, CONNECTION_TYPE.SEQUENCE_FLOW);
+	},
+	
+	drawMessageflow: function(waypoints, highLighted) {
+		var withArrowHead = true;
+		var conditional=isDefault=false;
+		this._drawFlow(waypoints, conditional, isDefault, highLighted, withArrowHead, CONNECTION_TYPE.MESSAGE_FLOW);
+	},
+	
+	drawAssociation: function(waypoints, withArrowHead, highLighted) {
+		var withArrowHead = withArrowHead;
+		var conditional=isDefault=false;
+		this._drawFlow(waypoints, conditional, isDefault, highLighted, withArrowHead, CONNECTION_TYPE.ASSOCIATION);
+	},
+  
+	_drawCircleTail: function(line, connectionType){
+		var diameter = ARROW_WIDTH/2*1.5;
+		
+		// anti smoothing
+		if (this.strokeWidth%2 == 1)
+			line.x1 += .5, line.y1 += .5;
+		
+		var circleTail = this.g.ellipse(line.x1, line.y1, diameter, diameter);
+		circleTail.attr("fill", Color.white);
+		circleTail.attr("stroke", this.getPaint());
+		
+		return circleTail;
+	},
+	
+	_drawArrowHead: function(line, connectionType){
+		var doubleArrowWidth = 2 * ARROW_WIDTH;
+		
+		if (connectionType == CONNECTION_TYPE.ASSOCIATION)
+			var arrowHead = this.g.path("M-" + (ARROW_WIDTH/2+.5) + " -" + doubleArrowWidth + "L 0 0 L" + (ARROW_WIDTH/2+.5) + " -" + doubleArrowWidth);
+		else
+			var arrowHead = this.g.path("M0 0L-" + (ARROW_WIDTH/2+.5) + " -" + doubleArrowWidth + "L" + (ARROW_WIDTH/2+.5) + " -" + doubleArrowWidth + "z");
+		
+		//arrowHead.transform("t" + 0 + ",-" + this.getStroke() + "");
+		
+		// anti smoothing
+		if (this.strokeWidth%2 == 1)
+			line.x2 += .5, line.y2 += .5;
+		
+		arrowHead.transform("t" + line.x2 + "," + line.y2 + "");
+		arrowHead.transform("...r" + Raphael.deg(line.angle - Math.PI / 2) + " " + 0 + " " + 0);
+		
+		if (!connectionType || connectionType == CONNECTION_TYPE.SEQUENCE_FLOW)
+			arrowHead.attr("fill", this.getPaint());
+		else if (connectionType == CONNECTION_TYPE.MESSAGE_FLOW)
+			arrowHead.attr("fill", Color.white);
+			
+		arrowHead.attr("stroke-width", this.getStroke());
+		arrowHead.attr("stroke", this.getPaint());
+		
+		return arrowHead;
+	},
+	
+	/*
+	drawArrowHead2: function(srcX, srcY, targetX, targetY) {
+		var doubleArrowWidth = 2 * ARROW_WIDTH;
+		
+		//var arrowHead = this.g.path("M-" + ARROW_WIDTH/2 + " -" + doubleArrowWidth + "L0 0" + "L" + ARROW_WIDTH/2 + " -" + doubleArrowWidth + "z");
+		
+		var arrowHead = this.g.path("M0 0L-" + ARROW_WIDTH/1.5 + " -" + doubleArrowWidth + "L" + ARROW_WIDTH/1.5 + " -" + doubleArrowWidth + "z");
+		//var c = DefaultProcessDiagramCanvas.g.ellipse(0, 0, 3, 3);
+		//c.transform("t"+targetX+","+targetY+"");
+		
+		var angle = Math.atan2(targetY - srcY, targetX - srcX);
+		
+		arrowHead.transform("t"+targetX+","+targetY+"");
+		arrowHead.transform("...r" + Raphael.deg(angle - Math.PI / 2) + " "+0+" "+0);
+		
+		//console.log(arrowHead.transform());
+		//console.log("--> " + Raphael.deg(angle - Math.PI / 2));
+		
+		arrowHead.attr("fill", this.getPaint());
+		arrowHead.attr("stroke", this.getPaint());
+		
+		/ *
+		// shaddow
+		var c0 = arrowHead.clone();
+		c0.transform("...t-1 1");
+		c0.attr("stroke-width", this.strokeWidth);
+		c0.attr("stroke", Color.black);
+		c0.attr("opacity", 0.15);
+		c0.toBack();
+		* /
+	},
+	*/
+	
+	_drawConditionalSequenceFlowIndicator: function(line){
+		var horizontal = (CONDITIONAL_INDICATOR_WIDTH * 0.7);
+		var halfOfHorizontal = horizontal / 2;
+		var halfOfVertical = CONDITIONAL_INDICATOR_WIDTH / 2;
+
+		var uuid = null;
+		var waypoints = [{x: 0, y: 0},
+						{x: -halfOfHorizontal, y: halfOfVertical},
+						{x: 0, y: CONDITIONAL_INDICATOR_WIDTH},
+						{x: halfOfHorizontal, y: halfOfVertical}];
+		/*
+		var polyline = new Polyline(uuid, waypoints, this.getStroke());
+		polyline.element = this.g.path(polyline.path);
+		polyline.element.attr("stroke-width", this.getStroke());
+		polyline.element.attr("stroke", this.getPaint());
+		polyline.element.id = uuid;
+		*/
+		var polygone = new Polygone(waypoints, this.getStroke());
+		polygone.element = this.g.path(polygone.path);
+		polygone.element.attr("fill", Color.white);
+		
+		polygone.transform("t" + line.x1 + "," + line.y1 + "");
+		polygone.transform("...r" + Raphael.deg(line.angle - Math.PI / 2) + " " + 0 + " " + 0);
+		
+		
+		var cosAngle = Math.cos((line.angle));
+		var sinAngle = Math.sin((line.angle));
+		
+		//polygone.element.attr("stroke-width", this.getStroke());
+		//polygone.element.attr("stroke", this.getPaint());
+		
+		polygone.attr({"stroke-width": this.getStroke(), "stroke": this.getPaint()});
+		
+		return polygone.element;
+	},
+  
+	drawSequenceflowWithoutArrow: function(waypoints, conditional, isDefault, highLighted) {
+		var withArrowHead = false;
+		this._drawFlow(waypoints, conditional, isDefault, highLighted, withArrowHead, CONNECTION_TYPE.SEQUENCE_FLOW);
+	},
+	
+	/*
+	 * Draw artifacts
+	 */
+	
+	drawPoolOrLane: function(x, y, width, height, name){
+		// anti smoothing
+		if (this.strokeWidth%2 == 1)
+			x = Math.round(x) + .5, y = Math.round(y) + .5;
+		
+		// shape
+		var rect = this.g.rect(x, y, width, height);
+		var attr = {"stroke-width": NORMAL_STROKE, stroke: TASK_STROKE_COLOR};
+		rect.attr(attr);
+		
+		// Add the name as text, vertical
+		if(name != null && name.length > 0) {
+			var attr = POOL_LANE_FONT;
+			
+			// Include some padding
+			var availableTextSpace = height - 6;
+			
+			// Create rotation for derived font
+			var truncated = this.fitTextToWidth(name, availableTextSpace);
+			var realWidth = this.getStringWidth(truncated, attr);
+			var realHeight = this.getStringHeight(truncated, attr);
+			
+			//console.log("truncated:", truncated, ", height:", height, ", realHeight:", realHeight, ", availableTextSpace:", availableTextSpace, ", realWidth:", realWidth);
+			var newX = x + 2 + realHeight*1 - realHeight/2;
+			var newY = 3 + y + availableTextSpace - (availableTextSpace - realWidth) / 2 - realWidth/2;
+			var textElement = this.g.text(newX, newY, truncated).attr(attr);
+			//console.log(".getBBox(): ", t.getBBox());
+			textElement.transform("r" + Raphael.deg(270 * Math.PI/180) + " " + newX + " " + newY);
+		}
+		
+		// TODO: add to set
+	},
+	
+	_drawTask: function(name, x, y, width, height, thickBorder) {
+		var originalPaint = this.getPaint();
+		this.setPaint(TASK_COLOR);
+		
+		// anti smoothing
+		if (this.strokeWidth%2 == 1)
+			x = Math.round(x) + .5, y = Math.round(y) + .5;
+		
+		// shape
+		var shape = this.g.rect(x, y, width, height, TASK_CORNER_ROUND);
+		var attr = {"stroke-width": this.strokeWidth, stroke: TASK_STROKE_COLOR, fill: this.getPaint()};
+		shape.attr(attr);
+		//shape.attr({fill: "90-"+this.getPaint()+"-" + Color.get(250, 250, 244)});
+		
+		var contextObject = this.getConextObject();
+		if (contextObject) {
+			shape.id = contextObject.id;
+			shape.data("contextObject", contextObject);
+		}
+		
+		//var activity = this.getConextObject();
+		//console.log("activity: " + activity.getId(), activity);
+		//Object.clone(activity);
+		
+		/*
+		c.mouseover(function(){
+			this.attr({"stroke-width": NORMAL_STROKE + 2});
+		}).mouseout(function(){
+			this.attr({"stroke-width": NORMAL_STROKE});
+		});
+		*/
+		
+		this.setPaint(originalPaint);
+
+		// white shaddow
+		this.drawShaddow(shape);
+		
+		
+		if (thickBorder) {
+			shape.attr({"stroke-width": THICK_TASK_BORDER_STROKE});
+		} else {
+			//g.draw(rect);
+		}
+		
+		// text
+		if (name) {
+			var fontAttr = TASK_FONT;
+			
+			// Include some padding
+			var paddingX = 5;
+			var paddingY = 5;
+			var availableTextSpace = width - paddingX*2;
+			
+			// TODO: this.setFont
+			// var originalFont = this.getFont();
+			// this.setFont(TASK_FONT)
+			/*
+			var truncated = this.fitTextToWidth(name, availableTextSpace);
+			var realWidth = this.getStringWidth(truncated, fontAttr);
+			var realHeight = this.getStringHeight(truncated, fontAttr);
+			
+			//var t = this.g.text(x + width/2 + realWidth*0/2 + paddingX*0, y + height/2, truncated).attr(fontAttr);
+			*/
+			//console.log("draw task name: " + name);
+			var boxWidth = width - (2 * TEXT_PADDING);
+			var boxHeight = height - ICON_SIZE - ICON_PADDING - ICON_PADDING - MARKER_WIDTH - 2 - 2;
+			var boxX = x + width/2 - boxWidth/2;
+			var boxY = y + height/2 - boxHeight/2 + ICON_PADDING + ICON_PADDING - 2 - 2;
+			/*
+			var boxWidth = width - (2 * ANNOTATION_TEXT_PADDING);
+			var boxHeight = height - (2 * ANNOTATION_TEXT_PADDING);
+			var boxX = x + width/2 - boxWidth/2;
+			var boxY = y + height/2 - boxHeight/2;
+			*/
+			
+			this.drawTaskLabel(name, boxX, boxY, boxWidth, boxHeight);
+		}
+	},
+	
+	drawTaskLabel: function(text, x, y, boxWidth, boxHeight){
+		var originalFont = this.getFont();
+		this.setFont(TASK_FONT);
+			
+		this._drawMultilineText(text, x, y, boxWidth, boxHeight, MULTILINE_VERTICAL_ALIGN_MIDDLE, MULTILINE_HORIZONTAL_ALIGN_MIDDLE);
+		
+		this.setFont(originalFont);
+	},
+	
+	drawAnnotationText: function(text, x, y, width, height){
+		//this._drawMultilineText(text, x, y, width, height, "start");
+		
+		var originalPaint = this.getPaint();
+		var originalFont = this.getFont();
+		
+		this.setPaint(Color.black);
+		this.setFont(TASK_FONT);
+			
+		this._drawMultilineText(text, x, y, width, height, MULTILINE_VERTICAL_ALIGN_TOP, MULTILINE_HORIZONTAL_ALIGN_LEFT);
+		
+		this.setPaint(originalPaint);
+		this.setFont(originalFont);
+	},
+	
+	drawLabel: function(text, x, y, width, height){
+		//this._drawMultilineText(text, x, y, width, height, "start");
+		
+		var originalPaint = this.getPaint();
+		var originalFont = this.getFont();
+		
+		this.setPaint(LABEL_COLOR);
+		//this.setFont(LABEL_FONT);
+		this.setFont(LABEL_FONT_SMOOTH);
+		
+		// predefined box width for labels
+		// TODO: use label width as is, but not height (for stretching)
+		if (!width || !height) {
+		  width = 100;
+		  height = 0;
+		}
+		
+		// TODO: remove it. It is debug
+		x = x - width/2;
+	  
+		this._drawMultilineText(text, x, y, width, height, MULTILINE_VERTICAL_ALIGN_TOP, MULTILINE_HORIZONTAL_ALIGN_MIDDLE);
+		
+		this.setPaint(originalPaint);
+		this.setFont(originalFont);
+	},
+	
+	/*
+	drawMultilineLabel: function(text, x, y){
+		var originalFont = this.getFont();
+		this.setFont(LABEL_FONT_SMOOTH);
+		
+		var boxWidth = 80;
+		x = x - boxWidth/2
+		
+		this._drawMultilineText(text, x, y, boxWidth, null, "middle");
+		this.setFont(originalFont);
+	},
+	*/
+	
+	getStringWidth: function(text, fontAttrs){
+		var textElement = this.g.text(0, 0, text).attr(fontAttrs).hide();
+		var bb = textElement.getBBox();
+		
+		//console.log("string width: ", t.getBBox().width);
+		return textElement.getBBox().width;
+	},
+	getStringHeight: function(text, fontAttrs){
+		var textElement = this.g.text(0, 0, text).attr(fontAttrs).hide();
+		var bb = textElement.getBBox();
+		
+		//console.log("string height: ", t.getBBox().height);
+		return textElement.getBBox().height;
+	},
+	fitTextToWidth: function(original, width) {
+		var text = original;
+
+		// TODO: move attr on parameters
+		var attr = {font: "11px Arial", opacity: 0};
+		
+		// remove length for "..."
+		var dots = this.g.text(0, 0, "...").attr(attr).hide();
+		var dotsBB = dots.getBBox();
+		
+		var maxWidth = width - dotsBB.width;
+		
+		var textElement = this.g.text(0, 0, text).attr(attr).hide();
+		var bb = textElement.getBBox();
+		
+		// it's a little bit incorrect with "..."
+		while (bb.width > maxWidth && text.length > 0) {
+			text = text.substring(0, text.length - 1);
+			textElement.attr({"text": text});
+			bb = textElement.getBBox();
+		}
+
+		// remove element from paper
+		textElement.remove();
+		
+		if (text != original) {
+			text = text + "...";
+		}
+
+		return text;
+	},
+	wrapTextToWidth: function(original, width){
+	
+		//return original;
+		
+		var text = original;
+		var wrappedText = "\n";
+		
+		// TODO: move attr on parameters
+		var attr = {font: "11px Arial", opacity: 0};
+		
+		var textElement = this.g.text(0, 0, wrappedText).attr(attr).hide();
+		var bb = textElement.getBBox();
+		
+		var resultText = "";
+		var i = 0, j = 0;
+		while (text.length > 0) {
+			while (bb.width < width && text.length>0) {
+				// remove "\n"
+				wrappedText = wrappedText.substring(0,wrappedText.length-1);
+				// add new char, add "\n"
+				wrappedText = wrappedText + text.substring(0,1) + "\n";
+				text = text.substring(1);
+				
+				textElement.attr({"text": wrappedText});
+				bb = textElement.getBBox();
+				i++;
+				if (i>200) break;
+			}
+			// remove "\n"
+			wrappedText = wrappedText.substring(0, wrappedText.length - 1);
+			
+			if (text.length == 0) {
+				resultText += wrappedText;
+				break;
+			}
+			
+			// return last char to text
+			text = wrappedText.substring(wrappedText.length-1) + text;
+			// remove last char from wrappedText
+			wrappedText = wrappedText.substring(0, wrappedText.length-1) + "\n";
+			
+			textElement.attr({"text": wrappedText});
+			bb = textElement.getBBox();
+			
+			//console.log(">> ", wrappedText, ", ", text);
+			resultText += wrappedText;
+			wrappedText = "\n";
+			
+			j++;
+			if (j>20) break;
+		}
+		// remove element from paper
+		textElement.remove();
+		
+		return resultText;
+	},
+	
+	wrapTextToWidth2: function(original, width){
+		var text = original;
+		var wrappedText = "\n";
+		
+		// TODO: move attr on parameters
+		var attr = {font: "11px Arial", opacity: 0};
+		
+		var textElement = this.g.text(0, 0, wrappedText).attr(attr).hide();
+		var bb = textElement.getBBox();
+		
+		var resultText = "";
+		var i = 0, j = 0;
+		while (text.length > 0) {
+			while (bb.width < width && text.length>0) {
+				// remove "\n"
+				wrappedText = wrappedText.substring(0,wrappedText.length-1);
+				// add new char, add "\n"
+				wrappedText = wrappedText + text.substring(0,1) + "\n";
+				text = text.substring(1);
+				
+				textElement.attr({"text": wrappedText});
+				bb = textElement.getBBox();
+				i++;
+				if (i>200) break;
+			}
+			// remove "\n"
+			wrappedText = wrappedText.substring(0, wrappedText.length - 1);
+			
+			if (text.length == 0) {
+				resultText += wrappedText;
+				break;
+			}
+			
+			// return last char to text
+			text = wrappedText.substring(wrappedText.length-1) + text;
+			// remove last char from wrappedText
+			wrappedText = wrappedText.substring(0, wrappedText.length-1) + "\n";
+			
+			textElement.attr({"text": wrappedText});
+			bb = textElement.getBBox();
+			
+			//console.log(">> ", wrappedText, ", ", text);
+			resultText += wrappedText;
+			wrappedText = "\n";
+			
+			j++;
+			if (j>20) break;
+		}
+		// remove element from paper
+		textElement.remove();
+		
+		return resultText;
+	},
+	
+	drawUserTask: function(name, x, y, width, height) {
+	  this.g.setStart();
+		this._drawTask(name, x, y, width, height);
+		var img = this.g.image(USERTASK_IMAGE, x + ICON_PADDING, y + ICON_PADDING, ICON_SIZE, ICON_SIZE);
+		var set = this.g.setFinish();
+		this.addHandlers(set, x, y, width, height, "task");
+	},
+	
+	drawScriptTask: function(name, x, y, width, height) {
+	  this.g.setStart();
+		this._drawTask(name, x, y, width, height);
+		var img = this.g.image(SCRIPTTASK_IMAGE, x + ICON_PADDING, y + ICON_PADDING, ICON_SIZE, ICON_SIZE);
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "task");
+	},
+	
+	drawServiceTask: function(name, x, y, width, height) {
+	  this.g.setStart();
+		this._drawTask(name, x, y, width, height);
+		var img = this.g.image(SERVICETASK_IMAGE, x + ICON_PADDING, y + ICON_PADDING, ICON_SIZE, ICON_SIZE);
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "task");
+	},
+	
+	drawReceiveTask: function(name, x, y, width, height) {
+	  this.g.setStart();
+		this._drawTask(name, x, y, width, height);
+		var img = this.g.image(RECEIVETASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE);
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "task");
+	},
+	
+	drawSendTask: function(name, x, y, width, height) {
+	  this.g.setStart();
+		this._drawTask(name, x, y, width, height);
+		var img = this.g.image(SENDTASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE);
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "task");
+	},
+	
+	drawManualTask: function(name, x, y, width, height) {
+	  this.g.setStart();
+		this._drawTask(name, x, y, width, height);
+		var img = this.g.image(MANUALTASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE);
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "task");
+	},
+	
+	drawBusinessRuleTask: function(name, x, y, width, height) {
+	  this.g.setStart();
+		this._drawTask(name, x, y, width, height);
+		var img = this.g.image(BUSINESS_RULE_TASK_IMAGE, x + 7, y + 7, ICON_SIZE, ICON_SIZE);
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "task");
+	},
+	
+	drawExpandedSubProcess: function(name, x, y, width, height, isTriggeredByEvent){
+	  this.g.setStart();
+		// anti smoothing
+		if (this.strokeWidth%2 == 1)
+			x = Math.round(x) + .5, y = Math.round(y) + .5;
+		
+		// shape
+		var rect = this.g.rect(x, y, width, height, EXPANDED_SUBPROCESS_CORNER_ROUND);
+		
+		// Use different stroke (dashed)
+		if(isTriggeredByEvent) {
+			rect.attr(EVENT_SUBPROCESS_ATTRS);
+		} else {
+			rect.attr(EXPANDED_SUBPROCESS_ATTRS);
+		}
+		
+		this.setContextToElement(rect);
+		
+		var fontAttr = EXPANDED_SUBPROCESS_FONT;
+		
+		// Include some padding
+		var paddingX = 10;
+		var paddingY = 5;
+		var availableTextSpace = width - paddingX*2;
+		
+		var truncated = this.fitTextToWidth(name, availableTextSpace);
+		var realWidth = this.getStringWidth(truncated, fontAttr);
+		var realHeight = this.getStringHeight(truncated, fontAttr);
+		
+		var textElement = this.g.text(x + width/2 - realWidth*0/2 + 0*paddingX, y + realHeight/2 + paddingY, truncated).attr(fontAttr);
+		
+		var set = this.g.setFinish();
+		// TODO: Expanded Sub Process may has specific handlers
+		//this.addHandlers(set, x, y, width, height, "task");
+	},
+	
+	drawCollapsedSubProcess: function(name, x, y, width, height, isTriggeredByEvent) {
+	  this.g.setStart();
+	  this._drawCollapsedTask(name, x, y, width, height, false);
+	  var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "task");
+	},
+	
+	drawCollapsedCallActivity: function(name, x, y, width, height) {
+	  this.g.setStart();
+		this._drawCollapsedTask(name, x, y, width, height, true);
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "task");
+	},
+
+	_drawCollapsedTask: function(name, x, y, width, height, thickBorder) {
+		// The collapsed marker is now visualized separately
+		this._drawTask(name, x, y, width, height, thickBorder);
+	},
+	
+	drawCollapsedMarker: function(x, y, width, height){
+		// rectangle
+		var rectangleWidth = MARKER_WIDTH;
+		var rectangleHeight = MARKER_WIDTH;
+		
+		// anti smoothing
+		if (this.strokeWidth%2 == 1)
+			y += .5;
+		
+		var rect = this.g.rect(x + (width - rectangleWidth) / 2, y + height - rectangleHeight - 3, rectangleWidth, rectangleHeight);
+		
+		// plus inside rectangle
+		var cx = rect.attr("x") + rect.attr("width")/2;
+		var cy = rect.attr("y") + rect.attr("height")/2;
+		
+		var line = this.g.path(
+			"M" + cx + " " + (cy+2) + "L" + cx + " " + (cy-2) + 
+			"M" + (cx-2) + " " + cy + "L" + (cx+2) + " " + cy
+		).attr({"stroke-width": this.strokeWidth});
+		
+	},
+	
+	drawActivityMarkers: function(x, y, width, height, multiInstanceSequential, multiInstanceParallel, collapsed){
+		if (collapsed) {
+			if (!multiInstanceSequential && !multiInstanceParallel) {
+				this.drawCollapsedMarker(x, y, width, height);
+			} else {
+				this.drawCollapsedMarker(x - MARKER_WIDTH / 2 - 2, y, width, height);
+				if (multiInstanceSequential) {
+					console.log("is collapsed and multiInstanceSequential");
+					this.drawMultiInstanceMarker(true, x + MARKER_WIDTH / 2 + 2, y, width, height);
+				} else if (multiInstanceParallel) {
+					console.log("is collapsed and multiInstanceParallel");
+					this.drawMultiInstanceMarker(false, x + MARKER_WIDTH / 2 + 2, y, width, height);
+				}
+			}
+		} else {
+			if (multiInstanceSequential) {
+				console.log("is multiInstanceSequential");
+				this.drawMultiInstanceMarker(true, x, y, width, height);
+			} else if (multiInstanceParallel) {
+				console.log("is multiInstanceParallel");
+				this.drawMultiInstanceMarker(false, x, y, width, height);
+			}
+		}
+	},
+	
+	drawGateway: function(x, y, width, height) {
+		
+		var rhombus = this.g.path(	"M" + x + " " + (y + (height / 2)) + 
+									"L" + (x + (width / 2)) + " " + (y + height) + 
+									"L" + (x + width) + " " + (y + (height / 2)) +
+									"L" + (x + (width / 2)) + " " + y +
+									"z"
+								);
+		
+		// white shaddow
+		this.drawShaddow(rhombus);
+		
+		rhombus.attr("stroke-width", this.strokeWidth);
+		rhombus.attr("stroke", Color.SlateGrey);
+		rhombus.attr({fill: Color.white});
+		
+		this.setContextToElement(rhombus);
+		
+		return rhombus;
+	},
+	
+	drawParallelGateway: function(x, y, width, height) {
+	  this.g.setStart();
+	  
+		// rhombus
+		this.drawGateway(x, y, width, height);
+
+		// plus inside rhombus
+		var originalStroke = this.getStroke();
+		this.setStroke(GATEWAY_TYPE_STROKE);
+		
+		var plus = this.g.path(
+			"M" + (x + 10) + " " + (y + height / 2) + "L" + (x + width - 10) + " " + (y + height / 2) +	// horizontal
+			"M" + (x + width / 2) + " " + (y + height - 10) + "L" + (x + width / 2) + " " + (y + 10)	// vertical
+		);
+		plus.attr({"stroke-width": this.getStroke(), "stroke": this.getPaint()});
+		
+		this.setStroke(originalStroke);
+		
+		var set = this.g.setFinish();
+		this.addHandlers(set, x, y, width, height, "gateway");
+	},
+	
+	drawExclusiveGateway: function(x, y, width, height) {
+	  this.g.setStart();
+	  
+		// rhombus
+		var rhombus = this.drawGateway(x, y, width, height);
+
+		var quarterWidth = width / 4;
+		var quarterHeight = height / 4;
+		
+		// X inside rhombus
+		var originalStroke = this.getStroke();
+		this.setStroke(GATEWAY_TYPE_STROKE);
+		
+		var iks = this.g.path(
+			"M" + (x + quarterWidth + 3) + " " + (y + quarterHeight + 3) + "L" + (x + 3 * quarterWidth - 3) + " " + (y + 3 * quarterHeight - 3) + 
+			"M" + (x + quarterWidth + 3) + " " + (y + 3 * quarterHeight - 3) + "L" + (x + 3 * quarterWidth - 3) + " " + (y + quarterHeight + 3)
+		);
+		iks.attr({"stroke-width": this.getStroke(), "stroke": this.getPaint()});
+		
+		this.setStroke(originalStroke);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "gateway");
+	},
+	
+	drawInclusiveGateway: function(x, y, width, height){
+	  this.g.setStart();
+	  
+		// rhombus
+		this.drawGateway(x, y, width, height);
+		
+		var diameter = width / 4;
+		
+		// circle inside rhombus
+		var originalStroke = this.getStroke();
+		this.setStroke(GATEWAY_TYPE_STROKE);
+		var circle = this.g.ellipse(width/2 + x, height/2 + y, diameter, diameter);
+		circle.attr({"stroke-width": this.getStroke(), "stroke": this.getPaint()});
+		
+		this.setStroke(originalStroke);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "gateway");
+	},
+	
+	drawEventBasedGateway: function(x, y, width, height){
+	  this.g.setStart();
+	  
+		// rhombus
+		this.drawGateway(x, y, width, height);
+		
+		var diameter = width / 2;
+
+	    // rombus inside rhombus
+	    var originalStroke = this.getStroke();
+		this.setStroke(GATEWAY_TYPE_STROKE);
+	    
+	    
+	    // draw GeneralPath (polygon)
+	    var n=5;
+	    var angle = 2*Math.PI/n;
+	    var x1Points = [];
+	    var y1Points = [];
+		
+		for ( var index = 0; index < n; index++ ) {
+			var v = index*angle - Math.PI/2;
+			x1Points[index] = x + parseInt(Math.round(width/2)) + parseInt(Math.round((width/4)*Math.cos(v)));
+        	y1Points[index] = y + parseInt(Math.round(height/2)) + parseInt(Math.round((height/4)*Math.sin(v)));
+		}
+		//g.drawPolygon(x1Points, y1Points, n);
+		
+		var path = "";
+		for ( var index = 0; index < n; index++ ) {
+			if (index == 0) 
+				path += "M";
+			else 
+				path += "L";
+			path += x1Points[index] + "," + y1Points[index];
+		}
+		path += "z";
+		var polygone = this.g.path(path);
+		polygone.attr("stroke-width", this.strokeWidth);
+		polygone.attr("stroke", this.getPaint());
+		
+		this.setStroke(originalStroke);
+		
+		var set = this.g.setFinish();
+    this.addHandlers(set, x, y, width, height, "gateway");
+	},
+	
+	/*
+	*  drawMultiInstanceMarker
+	*  drawHighLight
+	*  highLightFlow
+	*/
+	
+	drawMultiInstanceMarker: function(sequential, x, y, width, height) {
+		var rectangleWidth = MARKER_WIDTH;
+		var rectangleHeight = MARKER_WIDTH;
+		
+		// anti smoothing
+		if (this.strokeWidth%2 == 1)
+			x += .5;//, y += .5;
+			
+		var lineX = x + (width - rectangleWidth) / 2;
+		var lineY = y + height - rectangleHeight - 3;
+		
+		var originalStroke = this.getStroke();
+		this.setStroke(MULTI_INSTANCE_STROKE);
+		
+		if (sequential) {
+			var line = this.g.path(
+				"M" + lineX + " " + lineY + 							"L" + (lineX + rectangleWidth) + " " + lineY + 
+				"M" + lineX + " " + (lineY + rectangleHeight / 2) + 	"L" + (lineX + rectangleWidth) + " " + (lineY + rectangleHeight / 2) + 
+				"M" + lineX + " " + (lineY + rectangleHeight) + 		"L" + (lineX + rectangleWidth) + " " + (lineY + rectangleHeight)
+			).attr({"stroke-width": this.strokeWidth});
+		} else {
+			var line = this.g.path(
+				"M" + lineX + " " + 							lineY + "L" + lineX + " " + 					(lineY + rectangleHeight) +
+				"M" + (lineX + rectangleWidth / 2) + " " + 	lineY + "L" + (lineX + rectangleWidth / 2) + " " + 	(lineY + rectangleHeight) + 
+				"M" + (lineX + rectangleWidth) + " " + 		lineY + "L" + (lineX + rectangleWidth) + " " + 		(lineY + rectangleHeight)
+			).attr({"stroke-width": this.strokeWidth});
+		}
+		
+		this.setStroke(originalStroke);
+	},
+	
+	drawHighLight: function(x, y, width, height){
+		var originalPaint = this.getPaint();
+		var originalStroke = this.getStroke();
+		
+		this.setPaint(HIGHLIGHT_COLOR);
+		this.setStroke(THICK_TASK_BORDER_STROKE);
+
+		//var c = this.g.rect(x - width/2 - THICK_TASK_BORDER_STROKE, y - height/2 - THICK_TASK_BORDER_STROKE, width + THICK_TASK_BORDER_STROKE*2, height + THICK_TASK_BORDER_STROKE*2, 5);
+		var rect = this.g.rect(x - THICK_TASK_BORDER_STROKE, y - THICK_TASK_BORDER_STROKE, width + THICK_TASK_BORDER_STROKE*2, height + THICK_TASK_BORDER_STROKE*2, TASK_CORNER_ROUND);
+		rect.attr("stroke-width", this.strokeWidth);
+		rect.attr("stroke", this.getPaint());
+		
+		this.setPaint(originalPaint);
+		this.setStroke(originalStroke);
+	},
+	
+	highLightActivity: function(activityId){
+		var shape = this.g.getById(activityId);
+		if (!shape) {
+			console.error("Activity " + activityId + " not found");
+			return;
+		}
+		
+		var contextObject = shape.data("contextObject");
+		if (contextObject)
+			console.log("--> highLightActivity: ["+contextObject.getProperty("type")+"], activityId: " + contextObject.getId());
+		else
+			console.log("--> highLightActivity: ", shape, shape.data("contextObject"));
+		
+		shape.attr("stroke-width", THICK_TASK_BORDER_STROKE);
+		shape.attr("stroke", HIGHLIGHT_COLOR);
+	},
+	
+	highLightFlow: function(flowId){
+		var shapeFlow = this.g.getById(flowId);
+		if (!shapeFlow) {
+			console.error("Flow " + flowId + " not found");
+			return;
+		}
+		
+		var contextObject = shapeFlow.data("contextObject");
+		if (contextObject)
+			console.log("--> highLightFlow: ["+contextObject.id+"] " + contextObject.flow);
+		//console.log("--> highLightFlow: ", flow.flow, flow.data("set"));
+		
+		var st = shapeFlow.data("set");
+		
+		st.attr("stroke-width", SEQUENCEFLOW_HIGHLIGHT_STROKE);
+		st.attr("stroke", HIGHLIGHT_COLOR);
+		var withArrowHead = shapeFlow.data("withArrowHead");
+		if (withArrowHead)
+			st[1].attr("fill", HIGHLIGHT_COLOR);
+		
+		st.forEach(function(el){
+			//console.log("---->", el);
+			//el.attr("")
+		});
+	},
+	
+
+	_drawClock: function(cx, cy, width, height){
+		
+		var circle = this.g.ellipse(cx, cy, 1, 1).attr({stroke:"none", fill: Color.get(232, 239, 241)});
+		//var c = this.g.ellipse(cx, cy, width, height).attr({stroke:"none", fill: Color.red});
+		//x = cx - width/2;
+		//y = cy - height/2;
+	
+		var clock = this.g.path(
+		/* outer circle */ "M15.5,2.374		C8.251,2.375,2.376,8.251,2.374,15.5		C2.376,22.748,8.251,28.623,15.5,28.627c7.249-0.004,13.124-5.879,13.125-13.127C28.624,8.251,22.749,2.375,15.5,2.374z" +
+		/* inner circle */ "M15.5,26.623	C8.909,26.615,4.385,22.09,4.375,15.5	C4.385,8.909,8.909,4.384,15.5,4.374c4.59,0.01,11.115,3.535,11.124,11.125C26.615,22.09,22.091,26.615,15.5,26.623z" +
+		/*  9 */ "M8.625,15.5c-0.001-0.552-0.448-0.999-1.001-1c-0.553,0-1,0.448-1,1c0,0.553,0.449,1,1,1C8.176,16.5,8.624,16.053,8.625,15.5z" +
+		/*  8 */ "M8.179,18.572c-0.478,0.277-0.642,0.889-0.365,1.367c0.275,0.479,0.889,0.641,1.365,0.365c0.479-0.275,0.643-0.887,0.367-1.367C9.27,18.461,8.658,18.297,8.179,18.572z" +
+		/* 10 */ "M9.18,10.696c-0.479-0.276-1.09-0.112-1.366,0.366s-0.111,1.09,0.365,1.366c0.479,0.276,1.09,0.113,1.367-0.366C9.821,11.584,9.657,10.973,9.18,10.696z" +
+		/*  2 */ "M22.822,12.428c0.478-0.275,0.643-0.888,0.366-1.366c-0.275-0.478-0.89-0.642-1.366-0.366c-0.479,0.278-0.642,0.89-0.366,1.367C21.732,12.54,22.344,12.705,22.822,12.428z" +
+		/*  7 */ "M12.062,21.455c-0.478-0.275-1.089-0.111-1.366,0.367c-0.275,0.479-0.111,1.09,0.366,1.365c0.478,0.277,1.091,0.111,1.365-0.365C12.704,22.344,12.54,21.732,12.062,21.455z" +
+		/* 11 */ "M12.062,9.545c0.479-0.276,0.642-0.888,0.366-1.366c-0.276-0.478-0.888-0.642-1.366-0.366s-0.642,0.888-0.366,1.366C10.973,9.658,11.584,9.822,12.062,9.545z" +
+		/*  4 */ "M22.823,18.572c-0.48-0.275-1.092-0.111-1.367,0.365c-0.275,0.479-0.112,1.092,0.367,1.367c0.477,0.275,1.089,0.113,1.365-0.365C23.464,19.461,23.3,18.848,22.823,18.572z" +
+		/*  2 */ "M19.938,7.813c-0.477-0.276-1.091-0.111-1.365,0.366c-0.275,0.48-0.111,1.091,0.366,1.367s1.089,0.112,1.366-0.366C20.581,8.702,20.418,8.089,19.938,7.813z" +
+		/*  3 */ "M23.378,14.5c-0.554,0.002-1.001,0.45-1.001,1c0.001,0.552,0.448,1,1.001,1c0.551,0,1-0.447,1-1C24.378,14.949,23.929,14.5,23.378,14.5z" +
+		/* arrows */ "M15.501,6.624c-0.552,0-1,0.448-1,1l-0.466,7.343l-3.004,1.96c-0.478,0.277-0.642,0.889-0.365,1.365c0.275,0.479,0.889,0.643,1.365,0.367l3.305-1.676C15.39,16.99,15.444,17,15.501,17c0.828,0,1.5-0.671,1.5-1.5l-0.5-7.876C16.501,7.072,16.053,6.624,15.501,6.624z" +
+		/*  9 */ "M15.501,22.377c-0.552,0-1,0.447-1,1s0.448,1,1,1s1-0.447,1-1S16.053,22.377,15.501,22.377z" +
+		/*  8 */ "M18.939,21.455c-0.479,0.277-0.643,0.889-0.366,1.367c0.275,0.477,0.888,0.643,1.366,0.365c0.478-0.275,0.642-0.889,0.366-1.365C20.028,21.344,19.417,21.18,18.939,21.455z" +
+		"");
+		clock.attr({fill: Color.black, stroke: "none"});
+		//clock.transform("t " + (cx-29.75/2) + " " + (cy-29.75/2));
+		//clock.transform("...s 0.85");
+		
+		//clock.transform("...s " + .85 + " " + .85);
+		clock.transform("t " + (-2.374) + " " + (-2.374)	);
+		clock.transform("...t -" + (15.5-2.374) + " -" + (15.5-2.374)	);
+		clock.transform("...s " + 1*(width/35) + " " + 1*(height/35));
+		clock.transform("...T " + cx + " " + cy);
+		//clock.transform("t " + (cx-width/2) + " " + (cy-height/2));
+		
+		//console.log(".getBBox(): ", clock.getBBox());
+		//console.log(".attr(): ", c.attrs);
+		circle.attr("rx", clock.getBBox().width/2);
+		circle.attr("ry", clock.getBBox().height/2);
+		
+		//return circle
+	},
+	
+	_drawPentagon: function(cx, cy, width, height, filled){
+		// draw GeneralPath (polygon)
+	    var n=5;
+	    var angle = 2*Math.PI/n;
+	    var waypoints = [];
+		
+		for ( var index = 0; index < n; index++ ) {
+			var v = index*angle - Math.PI/2;
+			var point = {};
+			point.x = -width*1.2/2 + parseInt(Math.round(width*1.2/2)) + parseInt(Math.round((width*1.2/4)*Math.cos(v)));
+        	point.y = -height*1.2/2 + parseInt(Math.round(height*1.2/2)) + parseInt(Math.round((height*1.2/4)*Math.sin(v)));
+			waypoints[index] = point;
+		}
+		
+		var polygone = new Polygone(waypoints, this.getStroke());
+		polygone.element = this.g.path(polygone.path);
+		if (filled)
+			polygone.element.attr("fill", Color.black);
+		else
+			polygone.element.attr("fill", Color.white);
+		
+		polygone.element.transform("s " + 1*(width/35) + " " + 1*(height/35));
+		polygone.element.transform("...T " + cx + " " + cy);
+	},
+	
+	//_drawMultilineText: function(text, x, y, boxWidth, boxHeight, textAnchor) {
+	_drawMultilineText: function(text, x, y, boxWidth, boxHeight, verticalAlign, horizontalAlign) {
+		if (!text || text == "") 
+			return;
+			
+		// Autostretch boxHeight if boxHeight is 0
+		if (boxHeight == 0)
+		  verticalAlign = MULTILINE_VERTICAL_ALIGN_TOP;	  
+	
+		//var TEXT_PADDING = 3;
+		var width = boxWidth;
+		if (boxHeight)
+			var height = boxHeight;
+	
+		var layouts = [];
+		
+		//var font = {font: "11px Arial", opacity: 1, "fill": LABEL_COLOR};
+		var font = this.getFont();
+		var measurer = new LineBreakMeasurer(this.g, x, y, text, font);
+		var lineHeight = measurer.rafaelTextObject.getBBox().height;
+		//console.log("text: ", text.replace(/\n/g, "?"));
+		
+		if (height) {
+			var availableLinesCount = parseInt(height/lineHeight);
+			//console.log("availableLinesCount: " + availableLinesCount);
+		}
+		
+		var i = 1;
+		while (measurer.getPosition() < measurer.text.getEndIndex()) {
+			var layout = measurer.nextLayout(width);
+			//console.log("LAYOUT: " + layout + ", getPosition: " + measurer.getPosition());
+			
+			if (layout != null) {
+				// TODO: and check if measurer has next layout. If no then don't draw  dots
+				if (!availableLinesCount || i < availableLinesCount) {
+					layouts.push(layout);
+				} else {
+					layouts.push(this.fitTextToWidth(layout + "...", boxWidth));
+					break;
+				}
+			}
+			i++;
+		};
+		//console.log(layouts);
+		
+		measurer.rafaelTextObject.attr({"text": layouts.join("\n")});
+		
+		if (horizontalAlign)
+			measurer.rafaelTextObject.attr({"text-anchor": horizontalAlign}); // end, middle, start
+			
+		var bb = measurer.rafaelTextObject.getBBox();
+		// TODO: there is somethin wrong with wertical align. May be: measurer.rafaelTextObject.attr({"y": y + height/2 - bb.height/2})
+		measurer.rafaelTextObject.attr({"y": y + bb.height/2});
+		//var bb = measurer.rafaelTextObject.getBBox();
+		
+		if (measurer.rafaelTextObject.attr("text-anchor") == MULTILINE_HORIZONTAL_ALIGN_MIDDLE )
+			measurer.rafaelTextObject.attr("x",  x +  boxWidth/2);
+		else if (measurer.rafaelTextObject.attr("text-anchor") == MULTILINE_HORIZONTAL_ALIGN_RIGHT )
+			measurer.rafaelTextObject.attr("x",  x +  boxWidth);
+		
+		var boxStyle = {stroke: Color.LightSteelBlue2, "stroke-width": 1.0, "stroke-dasharray": "- "};
+		//var box = this.g.rect(x+.5, y + .5, width, height).attr(boxStyle);
+		var textAreaCX = x + boxWidth/2;
+				var height = boxHeight;
+				if (!height) height = bb.height;
+				var textAreaCY = y + height/2;
+				var dotLeftTop = this.g.ellipse(x, y, 3, 3).attr({"stroke-width": 0, fill: Color.LightSteelBlue, stroke: "none"}).hide();
+				var dotCenter = this.g.ellipse(textAreaCX, textAreaCY, 3, 3).attr({fill: Color.LightSteelBlue2, stroke: "none"}).hide();
+
+				/*
+				// real bbox
+				var bb = measurer.rafaelTextObject.getBBox();
+				var rect = paper.rect(bb.x+.5, bb.y + .5, bb.width, bb.height).attr({"stroke-width": 1});
+				*/
+				var rect = this.g.rect(x, y, boxWidth, height).attr({"stroke-width": 1}).attr(boxStyle).hide();
+				var debugSet = this.g.set();
+				debugSet.push(dotLeftTop, dotCenter, rect);
+				//debugSet.show();
+	},
+	
+	drawTextAnnotation: function(text, x, y, width, height){
+		var lineLength = 18;
+		var path = [];
+		  path.push(["M", x + lineLength, y]);
+		  path.push(["L", x, y]);
+		  path.push(["L", x, y + height]);
+		  path.push(["L", x + lineLength, y + height]);
+		  
+		  path.push(["L", x + lineLength, y + height -1]);
+		  path.push(["L", x + 1, y + height -1]);
+		  path.push(["L", x + 1, y + 1]);
+		  path.push(["L", x + lineLength, y + 1]);
+		  path.push(["z"]);
+	
+		var textAreaLines = this.g.path(path);
+		
+	  var boxWidth = width - (2 * ANNOTATION_TEXT_PADDING);
+      var boxHeight = height - (2 * ANNOTATION_TEXT_PADDING);
+      var boxX = x + width/2 - boxWidth/2;
+      var boxY = y + height/2 - boxHeight/2;
+      
+      // for debug
+          var rectStyle = {stroke: Color(112, 146, 190), "stroke-width": 1.0, "stroke-dasharray": "- "};
+          var r = this.g.rect(boxX, boxY, boxWidth, boxHeight).attr(rectStyle);
+	  //
+      
+	  this.drawAnnotationText(text, boxX, boxY, boxWidth, boxHeight);
+	},
+	
+	drawLabel111111111: function(text, x, y, width, height, labelAttrs){
+		var  debug = false;
+		
+		// text
+		if (text != null && text != undefined && text != "") {
+			var attr = LABEL_FONT;
+			
+			//console.log("x", x, "y", y, "width", width, "height", height );
+			
+			wrappedText = text;
+			if (labelAttrs && labelAttrs.wrapWidth) {
+				wrappedText = this.wrapTextToWidth(wrappedText, labelAttrs.wrapWidth);
+			}
+			var realWidth = this.getStringWidth(wrappedText, attr);
+			var realHeight = this.getStringHeight(wrappedText, attr);
+			
+			var textAreaCX = x + width/2;
+			var textAreaCY = y + 3 + height + this.getStringHeight(wrappedText, attr)/2;
+			
+			var textX = textAreaCX;
+			var textY = textAreaCY;
+
+			var textAttrs = {};
+			if (labelAttrs && labelAttrs.align) {
+				switch (labelAttrs.align) {
+					case "left": 
+						textAttrs["text-anchor"] = "start"; 
+						textX = textX - realWidth/2;
+					break;
+					case "center": 
+						textAttrs["text-anchor"] = "middle"; 
+					break;
+					case "right": 
+						textAttrs["text-anchor"] = "end"; 
+						textX = textX + realWidth/2;
+					break;
+				}
+			}
+			if (labelAttrs && labelAttrs.wrapWidth) {
+				if (true) {
+					// Draw frameborder
+					var textAreaStyle = {stroke: Color.LightSteelBlue2, "stroke-width": 1.0, "stroke-dasharray": "- "};
+					var textAreaX = textAreaCX - realWidth/2;
+					var textAreaY = textAreaCY+.5 - realHeight/2;
+					var textArea = this.g.rect(textAreaX, textAreaY, realWidth, realHeight).attr(textAreaStyle);
+					
+					var textAreaLines = this.g.path("M" + textAreaX + " " + textAreaY + "L" + (textAreaX+realWidth) + " " + (textAreaY+realHeight) + "M" +  + (textAreaX+realWidth) + " " + textAreaY + "L" + textAreaX + " " + (textAreaY+realHeight));
+					textAreaLines.attr(textAreaStyle);
+					
+					this.g.ellipse(textAreaCX, textAreaCY, 3, 3).attr({fill: Color.LightSteelBlue2, stroke: "none"});
+				}
+			}
+			
+			var label = this.g.text(textX, textY, wrappedText).attr(attr).attr(textAttrs);
+			//label.id = Raphael.createUUID();
+			//console.log("label ", label.id, ", ", wrappedText);
+			
+			if (this.fontSmoothing) {
+				label.attr({stroke: LABEL_COLOR, "stroke-width":.4});
+			}
+			
+			// debug
+			if (debug) {
+				var imageAreaStyle = {stroke: Color.grey61, "stroke-width": 1.0, "stroke-dasharray": "- "};
+				var imageArea = this.g.rect(x+.5, y+.5, width, height).attr(imageAreaStyle);
+				var imageAreaLines = this.g.path("M" + x + " " + y + "L" + (x+width) + " " + (y+height) + "M" +  + (x+width) + " " + y + "L" + x + " " + (y+height));
+				imageAreaLines.attr(imageAreaStyle);
+				var dotStyle = {fill: Color.Coral, stroke: "none"};
+				this.g.ellipse(x, y, 3, 3).attr(dotStyle);
+				this.g.ellipse(x+width, y, 2, 2).attr(dotStyle);
+				this.g.ellipse(x+width, y+height, 2, 2).attr(dotStyle);
+				this.g.ellipse(x, y+height, 2, 2).attr(dotStyle);
+			}
+			
+			return label;
+		}
+	},
+	
+	vvoid: function(){}
+};

+ 1087 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/ProcessDiagramGenerator.js

@@ -0,0 +1,1087 @@
+ /**
+ * Class to generate an image based the diagram interchange information in a
+ * BPMN 2.0 process.
+ *
+ * @author (Javascript) Dmitry Farafonov
+ */
+ 
+var ProcessDiagramGenerator = {	
+	options: {},
+	
+	processDiagramCanvas: [],
+	
+	activityDrawInstructions:{},
+	
+	processDiagrams: {},
+	
+	diagramBreadCrumbs: null,
+	
+	init: function(){
+		// start event
+		this.activityDrawInstructions["startEvent"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawNoneStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+		
+		// start timer event
+		this.activityDrawInstructions["startTimerEvent"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			var isInterrupting = activityImpl.getProperty("isInterrupting");
+			processDiagramCanvas.drawTimerStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, activityImpl.getProperty("name"));
+		};
+		
+		// start event
+		this.activityDrawInstructions["messageStartEvent"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			var isInterrupting = activityImpl.getProperty("isInterrupting");
+			processDiagramCanvas.drawMessageStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, activityImpl.getProperty("name"));
+		};
+		
+		// start signal event
+		this.activityDrawInstructions["startSignalEvent"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			var isInterrupting = activityImpl.getProperty("isInterrupting");
+			processDiagramCanvas.drawSignalStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, activityImpl.getProperty("name"));
+		};
+		
+		// start multiple event
+		this.activityDrawInstructions["startMultipleEvent"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			var isInterrupting = activityImpl.getProperty("isInterrupting");
+			processDiagramCanvas.drawMultipleStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, activityImpl.getProperty("name"));
+		};
+		
+		// signal catch
+		this.activityDrawInstructions["intermediateSignalCatch"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			var isInterrupting = activityImpl.getProperty("isInterrupting");
+			processDiagramCanvas.drawCatchingSignalEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+			if (label)
+			  processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// message catch
+		this.activityDrawInstructions["intermediateMessageCatch"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			var isInterrupting = activityImpl.getProperty("isInterrupting");
+			processDiagramCanvas.drawCatchingMessageEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// multiple catch
+		this.activityDrawInstructions["intermediateMultipleCatch"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			var isInterrupting = activityImpl.getProperty("isInterrupting");
+			processDiagramCanvas.drawCatchingMultipleEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		
+		
+		// signal throw
+		this.activityDrawInstructions["intermediateSignalThrow"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawThrowingSignalEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), activityImpl.getProperty("name"));
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// message throw
+		this.activityDrawInstructions["intermediateMessageThrow"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawThrowingMessageEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), activityImpl.getProperty("name"));
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// multiple throw
+		this.activityDrawInstructions["intermediateMultipleThrow"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawThrowingMultipleEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), activityImpl.getProperty("name"));
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// none throw
+		this.activityDrawInstructions["intermediateThrowEvent"] = function() {
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawThrowingNoneEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), activityImpl.getProperty("name"));
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// end event
+		this.activityDrawInstructions["endEvent"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawNoneEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+		
+		// error end event
+		this.activityDrawInstructions["errorEndEvent"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawErrorEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), null);
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// message end event
+		this.activityDrawInstructions["messageEndEvent"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawMessageEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), null);
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// signal end event
+		this.activityDrawInstructions["signalEndEvent"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawSignalEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), null);
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// multiple end event
+		this.activityDrawInstructions["multipleEndEvent"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawMultipleEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), null);
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// terminate end event
+		this.activityDrawInstructions["terminateEndEvent"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawTerminateEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// error start event
+		this.activityDrawInstructions["errorStartEvent"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawErrorStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), activityImpl.getProperty("name"));
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// task
+		this.activityDrawInstructions["task"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			// TODO: 
+			//console.error("task is not implemented yet");
+			/*
+			var activityImpl = this;
+			processDiagramCanvas.drawTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), thickBorder);
+			*/
+		};
+		
+		
+		// user task
+		this.activityDrawInstructions["userTask"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawUserTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+		
+		// script task
+		this.activityDrawInstructions["scriptTask"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawScriptTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+		
+		// service task
+		this.activityDrawInstructions["serviceTask"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawServiceTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+
+		// receive task
+		this.activityDrawInstructions["receiveTask"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawReceiveTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+		
+		// send task
+		this.activityDrawInstructions["sendTask"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawSendTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+
+		// manual task
+		this.activityDrawInstructions["manualTask"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+		
+			processDiagramCanvas.drawManualTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+
+		// businessRuleTask task
+		this.activityDrawInstructions["businessRuleTask"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawBusinessRuleTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+
+		// exclusive gateway
+		this.activityDrawInstructions["exclusiveGateway"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawExclusiveGateway(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+		
+		// inclusive gateway
+		this.activityDrawInstructions["inclusiveGateway"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawInclusiveGateway(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+		
+		// parallel gateway
+		this.activityDrawInstructions["parallelGateway"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawParallelGateway(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+		
+		// eventBasedGateway
+		this.activityDrawInstructions["eventBasedGateway"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			processDiagramCanvas.drawEventBasedGateway(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+		
+		// Boundary timer
+		this.activityDrawInstructions["boundaryTimer"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			var isInterrupting = activityImpl.getProperty("isInterrupting");
+			processDiagramCanvas.drawCatchingTimerEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// Boundary catch error
+		this.activityDrawInstructions["boundaryError"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			var isInterrupting = activityImpl.getProperty("isInterrupting");
+			processDiagramCanvas.drawCatchingErrorEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+		
+		// Boundary signal event
+		this.activityDrawInstructions["boundarySignal"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			var isInterrupting = activityImpl.getProperty("isInterrupting");
+			processDiagramCanvas.drawCatchingSignalEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);
+			
+			var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+      if (label)
+        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+		};
+
+    // Boundary message event
+    this.activityDrawInstructions["boundaryMessage"] = function(){
+        var activityImpl = this.activity;
+        var processDiagramCanvas = this.processDiagramCanvas;
+        processDiagramCanvas.setConextObject(activityImpl);
+
+        var isInterrupting = activityImpl.getProperty("isInterrupting");
+        processDiagramCanvas.drawCatchingMessageEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);
+
+        var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
+        if (label)
+            processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
+    };
+		
+		// timer catch event
+		this.activityDrawInstructions["intermediateTimer"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			var isInterrupting = null;
+			processDiagramCanvas.drawCatchingTimerEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, activityImpl.getProperty("name"));
+		};
+		
+		// subprocess
+		this.activityDrawInstructions["subProcess"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			// TODO: 
+			
+			processDiagramCanvas.setConextObject(activityImpl);
+			
+			var isExpanded = activityImpl.getProperty("isExpanded");
+			var isTriggeredByEvent = activityImpl.getProperty("triggeredByEvent");
+			if(isTriggeredByEvent == undefined) {
+			  isTriggeredByEvent = true;
+			}
+			// TODO: check why isTriggeredByEvent = true when undefined
+			isTriggeredByEvent = false;
+			
+			if (isExpanded != undefined && isExpanded == false) {
+			  processDiagramCanvas.drawCollapsedSubProcess(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
+					  activityImpl.getWidth(), activityImpl.getHeight(), isTriggeredByEvent);
+			} else {
+			  processDiagramCanvas.drawExpandedSubProcess(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
+					  activityImpl.getWidth(), activityImpl.getHeight(), isTriggeredByEvent);
+			}
+			
+			//console.error("subProcess is not implemented yet");
+		};
+		
+		// call activity
+		this.activityDrawInstructions["callActivity"] = function(){
+			var activityImpl = this.activity;
+			var processDiagramCanvas = this.processDiagramCanvas;
+			processDiagramCanvas.setConextObject(activityImpl);
+			processDiagramCanvas.drawCollapsedCallActivity(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
+		};
+		
+		$(document).ready(function(){
+		  // Protect right click on SVG elements (and on canvas too)
+		  document.body.oncontextmenu = function(event) {
+		    if (window.event.srcElement.tagName == "shape" || window.event.srcElement.tagName == "DIV" && window.event.srcElement.parentElement.className == "diagram") {
+
+		      // IE DIAGRAM CANVAS OR SHAPE DETECTED!
+		      return false;
+		    }
+		    return (!Object.isSVGElement(window.event.srcElement));
+		  };
+		});
+	},
+	
+	 getActivitiLabel:function(activityImpl){
+	   /*
+	     TODO: Label object should be in activityImpl and looks like:
+	     {
+	       x: 250,
+	       y: 250,
+	       width: 80,
+	       height: 30
+	     }
+	     And then:
+	     if (!activityImpl.label)
+	       return null;
+	     var label = activityImpl.label;
+	     label.text = activityImpl.name;
+	     return label;
+	   */
+
+	   // But now default label for all events is:
+	   return {
+	     text: activityImpl.getProperty("name"),
+	     x: activityImpl.getX() + .5 + activityImpl.getWidth()/2,
+	     y: activityImpl.getY() + .5 + activityImpl.getHeight() + ICON_PADDING,
+	     width: 100,
+	     height: 0
+	   };
+	},
+		
+	generateDiagram: function(processDefinitionDiagramLayout){
+		// Init canvas
+		var processDefinitionId = processDefinitionDiagramLayout.processDefinition.id;
+		//console.log("Init canvas ", processDefinitionId);
+		
+		if (this.getProcessDiagram(processDefinitionId) != undefined) {
+			// TODO: may be reset canvas if exists.. Or just show
+			//console.log("ProcessDiagram '" + processDefinitionId + "' is already generated. Just show it.");
+			return;
+		}
+		var processDiagram = this.initProcessDiagramCanvas(processDefinitionDiagramLayout);
+		var processDiagramCanvas = processDiagram.diagramCanvas;
+		
+		// Draw pool shape, if process is participant in collaboration
+		
+		if(processDefinitionDiagramLayout.participantProcess != undefined) {
+		  //console.log("Draw pool shape");
+		  var pProc = processDefinitionDiagramLayout.participantProcess;
+		  processDiagramCanvas.drawPoolOrLane(pProc.x, pProc.y, pProc.width, pProc.height, pProc.name);
+		}
+		
+		var laneSets = processDefinitionDiagramLayout.laneSets;
+		var activities = processDefinitionDiagramLayout.activities;
+		var sequenceFlows = processDefinitionDiagramLayout.sequenceFlows;
+		
+		
+		pb1.set('value', 0);
+		var cnt = 0;
+		if (laneSets) 
+			for(var i in laneSets) {
+				cnt += laneSets[i].lanes.length;
+			}
+		if (activities) 
+			cnt += activities.length;
+		if (sequenceFlows) 
+			cnt += sequenceFlows.length;
+		var step = (cnt>0)? 100/cnt : 0;
+		var progress = 0;
+		//console.log("progress bar step: ", step);
+		
+		var task1 = new $.AsyncQueue();
+		
+			// Draw lanes
+			
+			task1.add(function (task1) {
+				if (!laneSets) laneSets = [];
+				//console.log("> draw lane sets, count:", laneSets.length)
+			});
+			
+			for(var i in laneSets) {
+				var laneSet = laneSets[i];
+				//laneSet.id, laneSet.name
+				
+				task1.add(laneSet.lanes,function (task1, lane) {
+					progress += step;
+					pb1.set('value', parseInt(progress));
+					
+					//console.log("--> laneId: " + lane.name + ", name: " + lane.name);
+					
+					processDiagramCanvas.drawPoolOrLane(lane.x, lane.y, lane.width, lane.height, lane.name);
+				});
+			}
+			
+			// Draw activities
+			
+			task1.add(function (task1) {
+				if (!activities) activities = [];
+				//console.log("> draw activities, count:", activities.length)
+			});
+			
+			var activitiesLength = activities.length;
+			task1.add(activities,function (task1, activityJson) {
+				var activity = new ActivityImpl(activityJson);
+				activitiesLength --;
+				progress += step;
+				pb1.set('value', parseInt(progress));
+				//console.log(activitiesLength, "--> activityId: " + activity.getId() + ", name: " + activity.getProperty("name"));
+				ProcessDiagramGenerator.drawActivity(processDiagramCanvas, activity);
+			});
+			
+			// Draw sequence-flows
+			
+			task1.add(function (task1) {
+				if (!sequenceFlows) sequenceFlows = [];
+				//console.log("> draw sequence flows, count:", sequenceFlows.length)
+			});
+			
+			var flowsLength = sequenceFlows.length;
+			task1.add(sequenceFlows,function (task1, flow) {
+				var waypoints = [];
+				for(var j in flow.xPointArray) {
+					waypoints[j] = {x: flow.xPointArray[j], y: flow.yPointArray[j]};
+				}
+				var isDefault = flow.isDefault;
+				var isConditional = flow.isConditional;
+				var isHighLighted = flow.isHighLighted;
+				
+				// TODO: add source and destination for sequence flows in REST
+				// parse for test
+					var f = flow.flow;
+					var matches = f.match(/\((.*)\)--.*-->\((.*)\)/);
+					var sourceActivityId, destinationActivityId;
+					if (matches != null) {
+						sourceActivityId = matches[1];
+						destinationActivityId = matches[2];
+					}
+					flow.sourceActivityId = sourceActivityId;
+					flow.destinationActivityId = destinationActivityId;
+				//
+				flowsLength--;
+				progress += step;
+				pb1.set('value', parseInt(progress));
+				
+				//console.log(flowsLength, "--> flow: " + flow.flow);
+				
+				processDiagramCanvas.setConextObject(flow);
+				processDiagramCanvas.drawSequenceflow(waypoints, isConditional, isDefault, isHighLighted);
+			});
+			
+			task1.onComplete(function(){
+				if (progress<100)
+					pb1.set('value', 100);
+				//console.log("COMPLETE!!!");
+					
+				//console.timeEnd('generateDiagram');
+			});
+			
+			task1.run();
+	},
+	
+	getProcessDiagram: function (processDefinitionId) {
+		return this.processDiagrams[processDefinitionId];
+	},
+	
+	initProcessDiagramCanvas: function (processDefinitionDiagramLayout) {
+		var minX = 0;
+		var maxX = 0;
+		var minY = 0;
+		var maxY = 0;
+		
+		if(processDefinitionDiagramLayout.participantProcess != undefined) {
+		  var pProc = processDefinitionDiagramLayout.participantProcess;
+		  
+		  minX = pProc.x;
+		  maxX = pProc.x + pProc.width;
+		  minY = pProc.y;
+		  maxY = pProc.y + pProc.height;
+		}
+
+		var activities = processDefinitionDiagramLayout.activities;
+		for(var i in activities) {
+			var activityJson = activities[i];
+			var activity = new ActivityImpl(activityJson);
+			
+			// width
+			if (activity.getX() + activity.getWidth() > maxX) {
+				maxX = activity.getX() + activity.getWidth();
+			}
+			if (activity.getX() < minX) {
+				minX = activity.getX();
+			}
+			// height
+			if (activity.getY() + activity.getHeight() > maxY) {
+				maxY = activity.getY() + activity.getHeight();
+			}
+			if (activity.getY() < minY) {
+				minY = activity.getY();
+			}
+		}
+		
+		var sequenceFlows = processDefinitionDiagramLayout.sequenceFlows;
+		for(var i in sequenceFlows) {
+			var flow = sequenceFlows[i];
+			var waypoints = [];
+			for(var j in flow.xPointArray) {
+				waypoints[j] = {x: flow.xPointArray[j], y: flow.yPointArray[j]};
+				
+				// width
+				if (waypoints[j].x > maxX) {
+					maxX = waypoints[j].x;
+				}
+				if (waypoints[j].x < minX) {
+					minX = waypoints[j].x;
+				}
+				// height
+				if (waypoints[j].y > maxY) {
+					maxY = waypoints[j].y;
+				}
+				if (waypoints[j].y < minY) {
+					minY = waypoints[j].y;
+				}
+			}
+		}
+		
+		var laneSets = processDefinitionDiagramLayout.laneSets;
+		for(var i in laneSets) {
+			var laneSet = laneSets[i];
+			//laneSet.id, laneSet.name
+			
+			for(var j in laneSet.lanes) {
+				var lane = laneSet.lanes[j];
+				// width
+				if (lane.x + lane.width > maxX) {
+				  maxX = lane.x + lane.width;
+				}
+				if (lane.x < minX) {
+				  minX = lane.x;
+				}
+				// height
+				if (lane.y + lane.height > maxY) {
+				  maxY = lane.y + lane.height;
+				}
+				if (lane.y < minY) {
+				  minY = lane.y;
+				}
+			}
+		}
+	
+		var diagramCanvas = new ProcessDiagramCanvas();
+		if (diagramCanvas) {
+			
+			// create div in diagramHolder
+			var diagramHolder = document.getElementById(this.options.diagramHolderId);
+			if (!diagramHolder)
+				throw {msg: "Diagram holder not found", error: "diagramHolderNotFound"};
+			var div = document.createElement("DIV");
+			div.id = processDefinitionDiagramLayout.processDefinition.id;
+			div.className = "diagram";
+			diagramHolder.appendChild(div);
+			
+			diagramCanvas.init(maxX + 20, maxY + 20, processDefinitionDiagramLayout.processDefinition.id);
+			this.processDiagrams[processDefinitionDiagramLayout.processDefinition.id] = {
+				processDefinitionDiagramLayout: processDefinitionDiagramLayout,
+				diagramCanvas: diagramCanvas
+			};
+		}
+		return this.getProcessDiagram(processDefinitionDiagramLayout.processDefinition.id);
+		//return new DefaultProcessDiagramCanvas(maxX + 10, maxY + 10, minX, minY);
+	},
+	
+	drawActivity: function(processDiagramCanvas, activity, highLightedActivities) {
+		var type = activity.getProperty("type");
+		var drawInstruction = this.activityDrawInstructions[type];
+		if (drawInstruction != null) {	
+			drawInstruction.apply({processDiagramCanvas:processDiagramCanvas, activity:activity});
+		} else {
+			//console.error("no drawInstruction for " + type + ": ", activity);
+		}
+		
+		// Actually draw the markers
+		if (activity.getProperty("multiInstance") != undefined || activity.getProperty("collapsed") != undefined) {
+			//console.log(activity.getProperty("name"), activity.properties);
+			var multiInstanceSequential = (activity.getProperty("multiInstance") == "sequential");
+			var multiInstanceParallel = (activity.getProperty("multiInstance") == "parrallel");
+			var collapsed = activity.getProperty("collapsed");
+				processDiagramCanvas.drawActivityMarkers(activity.getX(), activity.getY(), activity.getWidth(), activity.getHeight(), 
+					multiInstanceSequential, multiInstanceParallel, collapsed);
+		}
+		/*
+		processDiagramCanvas.drawActivityMarkers(activity.getX(), activity.getY(), activity.getWidth(), activity.getHeight(), multiInstanceSequential,
+              multiInstanceParallel, collapsed);
+		*/
+
+		// TODO: Draw highlighted activities if they are present
+		
+	},
+	
+	setHighLights: function(highLights){
+		if (highLights.processDefinitionId == undefined) {
+			//console.error("Process instance " + highLights.processInstanceId + " doesn't exist");
+			return;
+		}
+		
+		var processDiagram = this.getProcessDiagram(highLights.processDefinitionId);
+		if (processDiagram == undefined) {
+			//console.error("Process diagram " + highLights.processDefinitionId + " not found");
+			return;
+		}
+		
+		var processDiagramCanvas = processDiagram.diagramCanvas;
+		
+		// TODO: remove highLightes from all activities before set new highLight
+		for (var i in highLights.activities) {
+			var activityId = highLights.activities[i];
+			processDiagramCanvas.highLightActivity(activityId);
+		}
+		
+		// TODO: remove highLightes from all flows before set new highLight
+		for (var i in highLights.flows) {
+			var flowId = highLights.flows[i];
+			var object = processDiagramCanvas.g.getById(flowId);
+			var flow = object.data("contextObject");
+			flow.isHighLighted = true;
+			processDiagramCanvas.highLightFlow(flowId);
+		}
+	},
+	
+	drawHighLights: function(processInstanceId) {
+		// Load highLights for the processInstanceId
+		/*
+		var url = Lang.sub(this.options.processInstanceHighLightsUrl, {processInstanceId: processInstanceId});
+		$.ajax({
+			url: url,
+			type: 'GET',
+			dataType: 'json',
+			cache: false,
+			async: true,
+		}).done(function(data) {
+			var highLights = data;
+			if (!highLights) {
+				console.log("highLights not found");
+				return;
+			}
+			
+			console.log("highLights[" + highLights.processDefinitionId + "][" + processInstanceId + "]: ", highLights);
+			
+			ProcessDiagramGenerator.setHighLights(highLights);
+		}).fail(function(jqXHR, textStatus){
+			console.log('Get HighLights['+processDefinitionId+'] failure: ', textStatus, jqXHR);
+		});
+		*/
+		ActivitiRest.getHighLights(processInstanceId, this._drawHighLights);
+	},
+	_drawHighLights: function() {
+		var highLights = this.highLights;
+		ProcessDiagramGenerator.setHighLights(highLights);
+	},
+	
+	// Load processDefinition
+	
+	drawDiagram: function(processDefinitionId) {
+		// Hide all diagrams
+		var diagrams = $("#" + this.options.diagramHolderId + " div.diagram");
+		diagrams.addClass("hidden");
+	
+	
+		// If processDefinitionId doesn't contain ":" then it's a "processDefinitionKey", not an id.
+		// Get process definition by key
+		if (processDefinitionId.indexOf(":") < 0) {
+			ActivitiRest.getProcessDefinitionByKey(processDefinitionId, this._drawDiagram);
+		} else {
+			this._drawDiagram.apply({processDefinitionId: processDefinitionId});
+		}
+	},
+	_drawDiagram: function() {
+		var processDefinitionId = this.processDefinitionId;
+		
+		ProcessDiagramGenerator.addBreadCrumbsItem(processDefinitionId);
+		
+		
+		// Check if processDefinition is already loaded and rendered
+		
+		
+		var processDiagram = ProcessDiagramGenerator.getProcessDiagram(processDefinitionId);
+
+		if (processDiagram != undefined && processDiagram != null) {
+			//console.log("Process diagram " + processDefinitionId + " is already loaded");
+			//return;
+			
+			var diagram = document.getElementById(processDefinitionId);
+			$(diagram).removeClass("hidden");
+			
+			// Regenerate image
+			var processDefinitionDiagramLayout = processDiagram.processDefinitionDiagramLayout;
+			ProcessDiagramGenerator.generateDiagram(processDefinitionDiagramLayout);
+			
+			return;
+		}
+
+		//console.time('loadDiagram');
+		
+		// Load processDefinition
+		
+		ActivitiRest.getProcessDefinition(processDefinitionId, ProcessDiagramGenerator._generateDiagram);
+	},
+	_generateDiagram: function() {
+		var processDefinitionDiagramLayout = this.processDefinitionDiagramLayout;
+		
+		//console.log("process-definition-diagram-layout["+processDefinitionDiagramLayout.processDefinition.id+"]: ", processDefinitionDiagramLayout);
+		
+		//console.timeEnd('loadDiagram');
+		//console.time('generateDiagram');
+		
+		pb1.set('value', 0);
+		ProcessDiagramGenerator.generateDiagram(processDefinitionDiagramLayout);
+	},
+	
+	getProcessDefinitionByKey: function(processDefinitionKey) {
+		var url = Lang.sub(this.options.processDefinitionByKeyUrl, {processDefinitionKey: processDefinitionKey});
+		
+		var processDefinition;
+		$.ajax({
+			url: url,
+			type: 'POST',
+			dataType: 'json',
+			cache: false,
+			async: false
+		}).done(function(data) { 
+			//console.log("ajax returned data");
+			//console.log("ajax returned data:", data);
+			processDefinition = data;
+			if (!processDefinition) {
+				//console.error("Process definition '" + processDefinitionKey + "' not found");
+			}
+		}).fail(function(jqXHR, textStatus){
+			//console.error('Get diagram layout['+processDefinitionKey+'] failure: ', textStatus, jqXHR);
+		});
+		
+		if (processDefinition) {
+			//console.log("Get process definition by key '" + processDefinitionKey + "': ", processDefinition.id);
+			return processDefinition;
+		} else {
+			return null;
+		}
+	},
+	
+	addBreadCrumbsItem: function(processDefinitionId){
+		var TPL_UL_CONTAINER = '<ul></ul>',
+			TPL_LI_CONTAINER = '<li id="{id}", processDefinitionId="{processDefinitionId}"><span>{name}</span></li>';
+		
+		if (!this.diagramBreadCrumbs)
+			this.diagramBreadCrumbs = $("#" + this.options.diagramBreadCrumbsId);
+		if (!this.diagramBreadCrumbs) return;
+		
+		
+		var ul = this.diagramBreadCrumbs.find("ul");
+		//console.log("ul: ", ul);
+		if (ul.size() == 0) {
+			ul = $(TPL_UL_CONTAINER);
+			this.diagramBreadCrumbs.append(ul);
+			
+		}
+		var liListOld = ul.find("li");
+		//console.warn("liListOld", liListOld);
+		
+		// TODO: if there is any items after current then remove that before adding new item (m.b. it is a duplicate)
+		var currentBreadCrumbsItemId = this.currentBreadCrumbsItemId;
+			found = false;
+		liListOld.each(
+			function(index, item) {
+				//console.warn("item:", $(this));
+				if (!found && currentBreadCrumbsItemId == $(this).attr("id")) {
+					found = true;
+					return;
+				}
+				if (found) {
+					//console.warn("remove ", $(this).attr("id"));
+					$(this).remove();
+				}
+			}
+		);
+		
+		var liListNew = ul.find("li");
+		
+		//console.log("liListNew size: ", liListNew.size());
+		var values = {
+			id: 'breadCrumbsItem_' + liListNew.size(),
+			processDefinitionId: processDefinitionId,
+			name: processDefinitionId
+		};
+		
+		
+		var tpl = Lang.sub(TPL_LI_CONTAINER, values);
+		//console.log("tpl: ", tpl);
+		ul.append(tpl);
+		
+		var li = ul.find("#" + values.id);
+		//console.warn("li:", li);
+		$('#' + values.id).on('click', this._breadCrumbsItemClick);
+		
+		ul.find("li").removeClass("selected");
+		li.attr("num", liListNew.size());
+		li.addClass("selected");
+		this.currentBreadCrumbsItemId = li.attr("id");
+	},
+	_breadCrumbsItemClick: function(){
+		var li = $(this),
+			id = li.attr("id"),
+			processDefinitionId = li.attr("processDefinitionId");
+		//console.warn("_breadCrumbsItemClick: ", id, ", processDefinitionId: ", processDefinitionId);
+		
+		var ul = ProcessDiagramGenerator.diagramBreadCrumbs.one("ul");
+		ul.find("li").removeClass("selected");
+		li.addClass("selected");
+		ProcessDiagramGenerator.currentBreadCrumbsItemId = li.attr("id");
+		
+		// Hide all diagrams
+		var diagrams = $("#"+ProcessDiagramGenerator.options.diagramHolderId+" div.diagram");
+		diagrams.addClass("hidden");
+		
+		var processDiagram = ProcessDiagramGenerator.getProcessDiagram(processDefinitionId);
+		
+		var diagram = document.getElementById(processDefinitionId);
+		if (!diagram) return;
+		$(diagram).removeClass("hidden");
+		
+		// Regenerate image
+		var processDefinitionDiagramLayout = processDiagram.processDefinitionDiagramLayout;
+		ProcessDiagramGenerator.generateDiagram(processDefinitionDiagramLayout);
+	},
+	
+	showFlowInfo: function(flow){
+		var diagramInfo = $("#" + this.options.diagramInfoId);
+		if (!diagramInfo) return;
+		
+		var values = {
+			flow: flow.flow,
+			isDefault: (flow.isDefault)? "true":"",
+			isConditional: (flow.isConditional)? "true":"",
+			isHighLighted: (flow.isHighLighted)? "true":"",
+			sourceActivityId: flow.sourceActivityId,
+			destinationActivityId: flow.destinationActivityId
+		};
+		var TPL_FLOW_INFO = '<div>{flow}</div>' 
+				+ '<div><b>sourceActivityId</b>: {sourceActivityId}</div>'
+				+ '<div><b>destinationActivityId</b>: {destinationActivityId}</div>'
+				+ '<div><b>isDefault</b>: {isDefault}</div>'
+				+ '<div><b>isConditional</b>: {isConditional}</div>'
+				+ '<div><b>isHighLighted</b>: {isHighLighted}</div>';
+		var tpl = Lang.sub(TPL_FLOW_INFO, values);
+		//console.log("info: ", tpl);
+		diagramInfo.html(tpl);
+	},
+	
+	showActivityInfo: function(activity){
+		var diagramInfo = $("#" + this.options.diagramInfoId);
+		if (!diagramInfo) return;
+		
+		var values = {
+			activityId: activity.getId(),
+			name: activity.getProperty("name"),
+			type: activity.getProperty("type")
+		};
+		var TPL_ACTIVITY_INFO = '' 
+				+ '<div><b>activityId</b>: {activityId}</div>'
+				+ '<div><b>name</b>: {name}</div>'
+				+ '<div><b>type</b>: {type}</div>';
+		var TPL_CALLACTIVITY_INFO = ''
+				+ '<div><b>collapsed</b>: {collapsed}</div>'
+				+ '<div><b>processDefinitonKey</b>: {processDefinitonKey}</div>';
+		
+		var template = TPL_ACTIVITY_INFO;
+		if (activity.getProperty("type") == "callActivity") {
+			values.collapsed = activity.getProperty("collapsed");
+			values.processDefinitonKey = activity.getProperty("processDefinitonKey");
+			template += TPL_CALLACTIVITY_INFO;
+		} else if (activity.getProperty("type") == "callActivity") {
+		
+		}
+				
+		var tpl = Lang.sub(template, values);
+		//console.log("info: ", tpl);
+		diagramInfo.html(tpl);
+	},
+	
+	hideInfo: function(){
+	  var diagramInfo = $("#" + this.options.diagramInfoId);
+	  if (!diagramInfo) return;
+	  diagramInfo.html("");
+	},
+	
+	vvoid: function(){}
+};
+
+var Lang = {
+	SUBREGEX: /\{\s*([^\|\}]+?)\s*(?:\|([^\}]*))?\s*\}/g,
+	UNDEFINED: 'undefined',
+	isUndefined: function(o) {
+		return typeof o === Lang.UNDEFINED;
+	},
+	sub: function(s, o) {
+		return ((s.replace) ? s.replace(Lang.SUBREGEX, function(match, key) {
+			return (!Lang.isUndefined(o[key])) ? o[key] : match;
+		}) : s);
+	}
+};
+
+if (Lang.isUndefined(console)) {
+    console = { log: function() {}, warn: function() {}, error: function() {}};
+}
+ProcessDiagramGenerator.init();

+ 125 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/jquery/jquery.asyncqueue.js

@@ -0,0 +1,125 @@
+/*
+* This file is part of the jquery plugin "asyncQueue".
+*
+* (c) Sebastien Roch <roch.sebastien@gmail.com>
+* @author (parallel) Dmitry Farafonov
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+(function($){
+    $.AsyncQueue = function() {
+        var that = this,
+            queue = [],
+			completeFunc,
+            failureFunc,
+            paused = false,
+            lastCallbackData,
+            _run,
+			_complete,
+			inQueue = 0,
+			defaultTimeOut = 10;
+
+        _run = function() {
+            var f = queue.shift();
+
+            if (f) {
+				inQueue++;
+				setTimeout(function(){
+					f.fn.apply(that, [that]);
+				
+					if (!f.isParallel)
+						if (paused === false) {
+							_run();
+						}
+					inQueue --;
+					if (inQueue == 0 && queue.length == 0)
+						_complete();
+				}, f.timeOut);                
+				
+				if (f.isParallel)
+					if (paused === false) {
+						_run();
+					}
+            }
+        };
+		
+		_complete = function(){
+			if (completeFunc)
+					completeFunc.apply(that, [that]);
+		};
+
+		this.onComplete = function(func) {
+            completeFunc = func;
+        };
+		
+		this.onFailure = function(func) {
+            failureFunc = func;
+        };
+
+        this.add = function(func) {
+			// TODO: add callback for queue[i] complete
+			
+			var obj = arguments[0];
+			if (obj && Object.prototype.toString.call(obj) === "[object Array]") {
+				var fn = arguments[1];
+				var timeOut = (typeof(arguments[2]) != "undefined")? arguments[2] : defaultTimeOut;
+				if (typeof(fn) == "function") {
+					for(var i = 0; i < obj.length; i++) {
+						var f = function(objx){
+							queue.push({isParallel: true, fn: function(){fn.apply(that, [that, objx]);}, timeOut: timeOut});
+						}(obj[i])
+					}
+				}
+			} else {
+				var fn = arguments[0];
+				var timeOut = (typeof(arguments[1]) != "undefined")? arguments[2] : defaultTimeOut;
+				queue.push({isParallel: false, fn: func, timeOut: timeOut});
+			}
+            return this;
+        };
+		
+		this.addParallel = function(func, timeOut) {
+			// TODO: add callback for queue[i] complete
+			
+            queue.push({isParallel: true, fn: func, timeOut: timeOut});
+            return this;
+        };
+
+        this.storeData = function(dataObject) {
+            lastCallbackData = dataObject;
+            return this;
+        };
+
+        this.lastCallbackData = function () {
+            return lastCallbackData;
+        };
+
+        this.run = function({
+            paused = false;
+            _run();
+        };
+
+        this.pause = function () {
+            paused = true;
+            return this;
+        };
+
+        this.failure = function() {
+            paused = true;
+            if (failureFunc) {
+                var args = [that];
+                for(i = 0; i < arguments.length; i++) {
+                    args.push(arguments[i]);
+                }
+                failureFunc.apply(that, args);
+            }
+        };
+		
+		this.size = function(){
+			return queue.length;
+		};
+
+        return this;
+    }
+})(jQuery);

+ 9266 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/jquery/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 <tag> 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 = "   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+	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></: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 = "<div " + style + "><div></div></div>" +
+			"<table " + style + " cellpadding='0' cellspacing='0'>" +
+			"<tr><td></td></tr></table>";
+
+		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 = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
+		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 = "<div style='width:4px;'></div>";
+			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 = "<a name='" + id + "'/>";
+
+	// 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 = "<a href='#'></a>";
+
+	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 = "<p class='TEST'></p>";
+
+		// 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 = "<div class='test e'></div><div class='test'></div>";
+
+	// 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 = /<tbody/i,
+	rhtml = /<|&#?\w+;/,
+	rnoInnerhtml = /<(?:script|style)/i,
+	rnocache = /<(?:script|object|embed|option|style)/i,
+	rnoshimcache = new RegExp("<(?:" + nodeNames + ")", "i"),
+	// checked="checked" or checked
+	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+	rscriptType = /\/(java|ecma)script/i,
+	rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
+	wrapMap = {
+		option: [ 1, "<select multiple='multiple'>", "</select>" ],
+		legend: [ 1, "<fieldset>", "</fieldset>" ],
+		thead: [ 1, "<table>", "</table>" ],
+		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+		col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+		area: [ 1, "<map>", "</map>" ],
+		_default: [ 0, "", "" ]
+	},
+	safeFragment = createSafeFragment( document );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+	wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+	text: function( text ) {
+		if ( jQuery.isFunction(text) ) {
+			return this.each(function(i) {
+				var self = jQuery( this );
+
+				self.text( text.call(this, i, self.text()) );
+			});
+		}
+
+		if ( typeof text !== "object" && text !== undefined ) {
+			return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+		}
+
+		return jQuery.text( this );
+	},
+
+	wrapAll: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapAll( html.call(this, i) );
+			});
+		}
+
+		if ( this[0] ) {
+			// The elements to wrap the target around
+			var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+			if ( this[0].parentNode ) {
+				wrap.insertBefore( this[0] );
+			}
+
+			wrap.map(function() {
+				var elem = this;
+
+				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+					elem = elem.firstChild;
+				}
+
+				return elem;
+			}).append( this );
+		}
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapInner( html.call(this, i) );
+			});
+		}
+
+		return this.each(function() {
+			var self = jQuery( this ),
+				contents = self.contents();
+
+			if ( contents.length ) {
+				contents.wrapAll( html );
+
+			} else {
+				self.append( html );
+			}
+		});
+	},
+
+	wrap: function( html ) {
+		var isFunction = jQuery.isFunction( html );
+
+		return this.each(function(i) {
+			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+		});
+	},
+
+	unwrap: function() {
+		return this.parent().each(function() {
+			if ( !jQuery.nodeName( this, "body" ) ) {
+				jQuery( this ).replaceWith( this.childNodes );
+			}
+		}).end();
+	},
+
+	append: function() {
+		return this.domManip(arguments, true, function( elem ) {
+			if ( this.nodeType === 1 ) {
+				this.appendChild( elem );
+			}
+		});
+	},
+
+	prepend: function() {
+		return this.domManip(arguments, true, function( elem ) {
+			if ( this.nodeType === 1 ) {
+				this.insertBefore( elem, this.firstChild );
+			}
+		});
+	},
+
+	before: function() {
+		if ( this[0] && this[0].parentNode ) {
+			return this.domManip(arguments, false, function( elem ) {
+				this.parentNode.insertBefore( elem, this );
+			});
+		} else if ( arguments.length ) {
+			var set = jQuery.clean( arguments );
+			set.push.apply( set, this.toArray() );
+			return this.pushStack( set, "before", arguments );
+		}
+	},
+
+	after: function() {
+		if ( this[0] && this[0].parentNode ) {
+			return this.domManip(arguments, false, function( elem ) {
+				this.parentNode.insertBefore( elem, this.nextSibling );
+			});
+		} else if ( arguments.length ) {
+			var set = this.pushStack( this, "after", arguments );
+			set.push.apply( set, jQuery.clean(arguments) );
+			return set;
+		}
+	},
+
+	// keepData is for internal use only--do not document
+	remove: function( selector, keepData ) {
+		for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+			if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+				if ( !keepData && elem.nodeType === 1 ) {
+					jQuery.cleanData( elem.getElementsByTagName("*") );
+					jQuery.cleanData( [ elem ] );
+				}
+
+				if ( elem.parentNode ) {
+					elem.parentNode.removeChild( elem );
+				}
+			}
+		}
+
+		return this;
+	},
+
+	empty: function() {
+		for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+			// Remove element nodes and prevent memory leaks
+			if ( elem.nodeType === 1 ) {
+				jQuery.cleanData( elem.getElementsByTagName("*") );
+			}
+
+			// Remove any remaining nodes
+			while ( elem.firstChild ) {
+				elem.removeChild( elem.firstChild );
+			}
+		}
+
+		return this;
+	},
+
+	clone: function( dataAndEvents, deepDataAndEvents ) {
+		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+		return this.map( function () {
+			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+		});
+	},
+
+	html: function( value ) {
+		if ( value === undefined ) {
+			return this[0] && this[0].nodeType === 1 ?
+				this[0].innerHTML.replace(rinlinejQuery, "") :
+				null;
+
+		// See if we can take a shortcut and just use innerHTML
+		} else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+			(jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
+			!wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
+
+			value = value.replace(rxhtmlTag, "<$1></$2>");
+
+			try {
+				for ( var i = 0, l = this.length; i < l; i++ ) {
+					// Remove element nodes and prevent memory leaks
+					if ( this[i].nodeType === 1 ) {
+						jQuery.cleanData( this[i].getElementsByTagName("*") );
+						this[i].innerHTML = value;
+					}
+				}
+
+			// If using innerHTML throws an exception, use the fallback method
+			} catch(e) {
+				this.empty().append( value );
+			}
+
+		} else if ( jQuery.isFunction( value ) ) {
+			this.each(function(i){
+				var self = jQuery( this );
+
+				self.html( value.call(this, i, self.html()) );
+			});
+
+		} else {
+			this.empty().append( value );
+		}
+
+		return this;
+	},
+
+	replaceWith: function( value ) {
+		if ( this[0] && this[0].parentNode ) {
+			// Make sure that the elements are removed from the DOM before they are inserted
+			// this can help fix replacing a parent with child elements
+			if ( jQuery.isFunction( value ) ) {
+				return this.each(function(i) {
+					var self = jQuery(this), old = self.html();
+					self.replaceWith( value.call( this, i, old ) );
+				});
+			}
+
+			if ( typeof value !== "string" ) {
+				value = jQuery( value ).detach();
+			}
+
+			return this.each(function() {
+				var next = this.nextSibling,
+					parent = this.parentNode;
+
+				jQuery( this ).remove();
+
+				if ( next ) {
+					jQuery(next).before( value );
+				} else {
+					jQuery(parent).append( value );
+				}
+			});
+		} else {
+			return this.length ?
+				this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+				this;
+		}
+	},
+
+	detach: function( selector ) {
+		return this.remove( selector, true );
+	},
+
+	domManip: function( args, table, callback ) {
+		var results, first, fragment, parent,
+			value = args[0],
+			scripts = [];
+
+		// We can't cloneNode fragments that contain checked, in WebKit
+		if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+			return this.each(function() {
+				jQuery(this).domManip( args, table, callback, true );
+			});
+		}
+
+		if ( jQuery.isFunction(value) ) {
+			return this.each(function(i) {
+				var self = jQuery(this);
+				args[0] = value.call(this, i, table ? self.html() : undefined);
+				self.domManip( args, table, callback );
+			});
+		}
+
+		if ( this[0] ) {
+			parent = value && value.parentNode;
+
+			// If we're in a fragment, just use that instead of building a new one
+			if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+				results = { fragment: parent };
+
+			} else {
+				results = jQuery.buildFragment( args, this, scripts );
+			}
+
+			fragment = results.fragment;
+
+			if ( fragment.childNodes.length === 1 ) {
+				first = fragment = fragment.firstChild;
+			} else {
+				first = fragment.firstChild;
+			}
+
+			if ( first ) {
+				table = table && jQuery.nodeName( first, "tr" );
+
+				for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
+					callback.call(
+						table ?
+							root(this[i], first) :
+							this[i],
+						// Make sure that we do not leak memory by inadvertently discarding
+						// the original fragment (which might have attached data) instead of
+						// using it; in addition, use the original fragment object for the last
+						// item instead of first because it can end up being emptied incorrectly
+						// in certain situations (Bug #8070).
+						// Fragments from the fragment cache must always be cloned and never used
+						// in place.
+						results.cacheable || ( l > 1 && i < lastIndex ) ?
+							jQuery.clone( fragment, true, true ) :
+							fragment
+					);
+				}
+			}
+
+			if ( scripts.length ) {
+				jQuery.each( scripts, evalScript );
+			}
+		}
+
+		return this;
+	}
+});
+
+function root( elem, cur ) {
+	return jQuery.nodeName(elem, "table") ?
+		(elem.getElementsByTagName("tbody")[0] ||
+		elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+		elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+
+	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+		return;
+	}
+
+	var type, i, l,
+		oldData = jQuery._data( src ),
+		curData = jQuery._data( dest, oldData ),
+		events = oldData.events;
+
+	if ( events ) {
+		delete curData.handle;
+		curData.events = {};
+
+		for ( type in events ) {
+			for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+				jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data );
+			}
+		}
+	}
+
+	// make the cloned public data object a copy from the original
+	if ( curData.data ) {
+		curData.data = jQuery.extend( {}, curData.data );
+	}
+}
+
+function cloneFixAttributes( src, dest ) {
+	var nodeName;
+
+	// We do not need to do anything for non-Elements
+	if ( dest.nodeType !== 1 ) {
+		return;
+	}
+
+	// clearAttributes removes the attributes, which we don't want,
+	// but also removes the attachEvent events, which we *do* want
+	if ( dest.clearAttributes ) {
+		dest.clearAttributes();
+	}
+
+	// mergeAttributes, in contrast, only merges back on the
+	// original attributes, not the events
+	if ( dest.mergeAttributes ) {
+		dest.mergeAttributes( src );
+	}
+
+	nodeName = dest.nodeName.toLowerCase();
+
+	// IE6-8 fail to clone children inside object elements that use
+	// the proprietary classid attribute value (rather than the type
+	// attribute) to identify the type of content to display
+	if ( nodeName === "object" ) {
+		dest.outerHTML = src.outerHTML;
+
+	} else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+		// IE6-8 fails to persist the checked state of a cloned checkbox
+		// or radio button. Worse, IE6-7 fail to give the cloned element
+		// a checked appearance if the defaultChecked value isn't also set
+		if ( src.checked ) {
+			dest.defaultChecked = dest.checked = src.checked;
+		}
+
+		// IE6-7 get confused and end up setting the value of a cloned
+		// checkbox/radio button to an empty string instead of "on"
+		if ( dest.value !== src.value ) {
+			dest.value = src.value;
+		}
+
+	// IE6-8 fails to return the selected option to the default selected
+	// state when cloning options
+	} else if ( nodeName === "option" ) {
+		dest.selected = src.defaultSelected;
+
+	// IE6-8 fails to set the defaultValue to the correct value when
+	// cloning other types of input fields
+	} else if ( nodeName === "input" || nodeName === "textarea" ) {
+		dest.defaultValue = src.defaultValue;
+	}
+
+	// Event data gets referenced instead of copied if the expando
+	// gets copied too
+	dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+	var fragment, cacheable, cacheresults, doc,
+	first = args[ 0 ];
+
+	// nodes may contain either an explicit document object,
+	// a jQuery collection or context object.
+	// If nodes[0] contains a valid object to assign to doc
+	if ( nodes && nodes[0] ) {
+		doc = nodes[0].ownerDocument || nodes[0];
+	}
+
+	// Ensure that an attr object doesn't incorrectly stand in as a document object
+	// Chrome and Firefox seem to allow this to occur and will throw exception
+	// Fixes #8950
+	if ( !doc.createDocumentFragment ) {
+		doc = document;
+	}
+
+	// Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+	// Cloning options loses the selected state, so don't cache them
+	// IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+	// Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+	// Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+	if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&
+		first.charAt(0) === "<" && !rnocache.test( first ) &&
+		(jQuery.support.checkClone || !rchecked.test( first )) &&
+		(jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+		cacheable = true;
+
+		cacheresults = jQuery.fragments[ first ];
+		if ( cacheresults && cacheresults !== 1 ) {
+			fragment = cacheresults;
+		}
+	}
+
+	if ( !fragment ) {
+		fragment = doc.createDocumentFragment();
+		jQuery.clean( args, doc, fragment, scripts );
+	}
+
+	if ( cacheable ) {
+		jQuery.fragments[ first ] = cacheresults ? fragment : 1;
+	}
+
+	return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function( name, original ) {
+	jQuery.fn[ name ] = function( selector ) {
+		var ret = [],
+			insert = jQuery( selector ),
+			parent = this.length === 1 && this[0].parentNode;
+
+		if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+			insert[ original ]( this[0] );
+			return this;
+
+		} else {
+			for ( var i = 0, l = insert.length; i < l; i++ ) {
+				var elems = ( i > 0 ? this.clone(true) : this ).get();
+				jQuery( insert[i] )[ original ]( elems );
+				ret = ret.concat( elems );
+			}
+
+			return this.pushStack( ret, name, insert.selector );
+		}
+	};
+});
+
+function getAll( elem ) {
+	if ( typeof elem.getElementsByTagName !== "undefined" ) {
+		return elem.getElementsByTagName( "*" );
+
+	} else if ( typeof elem.querySelectorAll !== "undefined" ) {
+		return elem.querySelectorAll( "*" );
+
+	} else {
+		return [];
+	}
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+	if ( elem.type === "checkbox" || elem.type === "radio" ) {
+		elem.defaultChecked = elem.checked;
+	}
+}
+// Finds all inputs and passes them to fixDefaultChecked
+function findInputs( elem ) {
+	var nodeName = ( elem.nodeName || "" ).toLowerCase();
+	if ( nodeName === "input" ) {
+		fixDefaultChecked( elem );
+	// Skip scripts, get other children
+	} else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) {
+		jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+	}
+}
+
+// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js
+function shimCloneNode( elem ) {
+	var div = document.createElement( "div" );
+	safeFragment.appendChild( div );
+
+	div.innerHTML = elem.outerHTML;
+	return div.firstChild;
+}
+
+jQuery.extend({
+	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+		var srcElements,
+			destElements,
+			i,
+			// IE<=8 does not properly clone detached, unknown element nodes
+			clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ?
+				elem.cloneNode( true ) :
+				shimCloneNode( elem );
+
+		if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+				(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+			// IE copies events bound via attachEvent when using cloneNode.
+			// Calling detachEvent on the clone will also remove the events
+			// from the original. In order to get around this, we use some
+			// proprietary methods to clear the events. Thanks to MooTools
+			// guys for this hotness.
+
+			cloneFixAttributes( elem, clone );
+
+			// Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+			srcElements = getAll( elem );
+			destElements = getAll( clone );
+
+			// Weird iteration because IE will replace the length property
+			// with an element if you are cloning the body and one of the
+			// elements on the page has a name or id of "length"
+			for ( i = 0; srcElements[i]; ++i ) {
+				// Ensure that the destination node is not null; Fixes #9587
+				if ( destElements[i] ) {
+					cloneFixAttributes( srcElements[i], destElements[i] );
+				}
+			}
+		}
+
+		// Copy the events from the original to the clone
+		if ( dataAndEvents ) {
+			cloneCopyEvent( elem, clone );
+
+			if ( deepDataAndEvents ) {
+				srcElements = getAll( elem );
+				destElements = getAll( clone );
+
+				for ( i = 0; srcElements[i]; ++i ) {
+					cloneCopyEvent( srcElements[i], destElements[i] );
+				}
+			}
+		}
+
+		srcElements = destElements = null;
+
+		// Return the cloned set
+		return clone;
+	},
+
+	clean: function( elems, context, fragment, scripts ) {
+		var checkScriptType;
+
+		context = context || document;
+
+		// !context.createElement fails in IE with an error but returns typeof 'object'
+		if ( typeof context.createElement === "undefined" ) {
+			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+		}
+
+		var ret = [], j;
+
+		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+			if ( typeof elem === "number" ) {
+				elem += "";
+			}
+
+			if ( !elem ) {
+				continue;
+			}
+
+			// Convert html string into DOM nodes
+			if ( typeof elem === "string" ) {
+				if ( !rhtml.test( elem ) ) {
+					elem = context.createTextNode( elem );
+				} else {
+					// Fix "XHTML"-style tags in all browsers
+					elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+					// Trim whitespace, otherwise indexOf won't work as expected
+					var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(),
+						wrap = wrapMap[ tag ] || wrapMap._default,
+						depth = wrap[0],
+						div = context.createElement("div");
+
+					// Append wrapper element to unknown element safe doc fragment
+					if ( context === document ) {
+						// Use the fragment we've already created for this document
+						safeFragment.appendChild( div );
+					} else {
+						// Use a fragment created with the owner document
+						createSafeFragment( context ).appendChild( div );
+					}
+
+					// Go to html and back, then peel off extra wrappers
+					div.innerHTML = wrap[1] + elem + wrap[2];
+
+					// Move to the right depth
+					while ( depth-- ) {
+						div = div.lastChild;
+					}
+
+					// Remove IE's autoinserted <tbody> from table fragments
+					if ( !jQuery.support.tbody ) {
+
+						// String was a <table>, *may* have spurious <tbody>
+						var hasBody = rtbody.test(elem),
+							tbody = tag === "table" && !hasBody ?
+								div.firstChild && div.firstChild.childNodes :
+
+								// String was a bare <thead> or <tfoot>
+								wrap[1] === "<table>" && !hasBody ?
+									div.childNodes :
+									[];
+
+						for ( j = tbody.length - 1; j >= 0 ; --j ) {
+							if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+								tbody[ j ].parentNode.removeChild( tbody[ j ] );
+							}
+						}
+					}
+
+					// IE completely kills leading whitespace when innerHTML is used
+					if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+						div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+					}
+
+					elem = div.childNodes;
+				}
+			}
+
+			// Resets defaultChecked for any radios and checkboxes
+			// about to be appended to the DOM in IE 6/7 (#8060)
+			var len;
+			if ( !jQuery.support.appendChecked ) {
+				if ( elem[0] && typeof (len = elem.length) === "number" ) {
+					for ( j = 0; j < len; j++ ) {
+						findInputs( elem[j] );
+					}
+				} else {
+					findInputs( elem );
+				}
+			}
+
+			if ( elem.nodeType ) {
+				ret.push( elem );
+			} else {
+				ret = jQuery.merge( ret, elem );
+			}
+		}
+
+		if ( fragment ) {
+			checkScriptType = function( elem ) {
+				return !elem.type || rscriptType.test( elem.type );
+			};
+			for ( i = 0; ret[i]; i++ ) {
+				if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+					scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+
+				} else {
+					if ( ret[i].nodeType === 1 ) {
+						var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType );
+
+						ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+					}
+					fragment.appendChild( ret[i] );
+				}
+			}
+		}
+
+		return ret;
+	},
+
+	cleanData: function( elems ) {
+		var data, id,
+			cache = jQuery.cache,
+			special = jQuery.event.special,
+			deleteExpando = jQuery.support.deleteExpando;
+
+		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+			if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+				continue;
+			}
+
+			id = elem[ jQuery.expando ];
+
+			if ( id ) {
+				data = cache[ id ];
+
+				if ( data && data.events ) {
+					for ( var type in data.events ) {
+						if ( special[ type ] ) {
+							jQuery.event.remove( elem, type );
+
+						// This is a shortcut to avoid jQuery.event.remove's overhead
+						} else {
+							jQuery.removeEvent( elem, type, data.handle );
+						}
+					}
+
+					// Null the DOM reference to avoid IE6/7/8 leak (#7054)
+					if ( data.handle ) {
+						data.handle.elem = null;
+					}
+				}
+
+				if ( deleteExpando ) {
+					delete elem[ jQuery.expando ];
+
+				} else if ( elem.removeAttribute ) {
+					elem.removeAttribute( jQuery.expando );
+				}
+
+				delete cache[ id ];
+			}
+		}
+	}
+});
+
+function evalScript( i, elem ) {
+	if ( elem.src ) {
+		jQuery.ajax({
+			url: elem.src,
+			async: false,
+			dataType: "script"
+		});
+	} else {
+		jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
+	}
+
+	if ( elem.parentNode ) {
+		elem.parentNode.removeChild( elem );
+	}
+}
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+	ropacity = /opacity=([^)]*)/,
+	// fixed for IE9, see #8346
+	rupper = /([A-Z]|^ms)/g,
+	rnumpx = /^-?\d+(?:px)?$/i,
+	rnum = /^-?\d/,
+	rrelNum = /^([\-+])=([\-+.\de]+)/,
+
+	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+	cssWidth = [ "Left", "Right" ],
+	cssHeight = [ "Top", "Bottom" ],
+	curCSS,
+
+	getComputedStyle,
+	currentStyle;
+
+jQuery.fn.css = function( name, value ) {
+	// Setting 'undefined' is a no-op
+	if ( arguments.length === 2 && value === undefined ) {
+		return this;
+	}
+
+	return jQuery.access( this, name, value, true, function( elem, name, value ) {
+		return value !== undefined ?
+			jQuery.style( elem, name, value ) :
+			jQuery.css( elem, name );
+	});
+};
+
+jQuery.extend({
+	// Add in style property hooks for overriding the default
+	// behavior of getting and setting a style property
+	cssHooks: {
+		opacity: {
+			get: function( elem, computed ) {
+				if ( computed ) {
+					// We should always get a number back from opacity
+					var ret = curCSS( elem, "opacity", "opacity" );
+					return ret === "" ? "1" : ret;
+
+				} else {
+					return elem.style.opacity;
+				}
+			}
+		}
+	},
+
+	// Exclude the following css properties to add px
+	cssNumber: {
+		"fillOpacity": true,
+		"fontWeight": true,
+		"lineHeight": true,
+		"opacity": true,
+		"orphans": true,
+		"widows": true,
+		"zIndex": true,
+		"zoom": true
+	},
+
+	// Add in properties whose names you wish to fix before
+	// setting or getting the value
+	cssProps: {
+		// normalize float css property
+		"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+	},
+
+	// Get and set the style property on a DOM Node
+	style: function( elem, name, value, extra ) {
+		// Don't set styles on text and comment nodes
+		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+			return;
+		}
+
+		// Make sure that we're working with the right name
+		var ret, type, origName = jQuery.camelCase( name ),
+			style = elem.style, hooks = jQuery.cssHooks[ origName ];
+
+		name = jQuery.cssProps[ origName ] || origName;
+
+		// Check if we're setting a value
+		if ( value !== undefined ) {
+			type = typeof value;
+
+			// convert relative number strings (+= or -=) to relative numbers. #7345
+			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+				value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
+				// Fixes bug #9237
+				type = "number";
+			}
+
+			// Make sure that NaN and null values aren't set. See: #7116
+			if ( value == null || type === "number" && isNaN( value ) ) {
+				return;
+			}
+
+			// If a number was passed in, add 'px' to the (except for certain CSS properties)
+			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+				value += "px";
+			}
+
+			// If a hook was provided, use that value, otherwise just set the specified value
+			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+				// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+				// Fixes bug #5509
+				try {
+					style[ name ] = value;
+				} catch(e) {}
+			}
+
+		} else {
+			// If a hook was provided get the non-computed value from there
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+				return ret;
+			}
+
+			// Otherwise just get the value from the style object
+			return style[ name ];
+		}
+	},
+
+	css: function( elem, name, extra ) {
+		var ret, hooks;
+
+		// Make sure that we're working with the right name
+		name = jQuery.camelCase( name );
+		hooks = jQuery.cssHooks[ name ];
+		name = jQuery.cssProps[ name ] || name;
+
+		// cssFloat needs a special treatment
+		if ( name === "cssFloat" ) {
+			name = "float";
+		}
+
+		// If a hook was provided get the computed value from there
+		if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
+			return ret;
+
+		// Otherwise, if a way to get the computed value exists, use that
+		} else if ( curCSS ) {
+			return curCSS( elem, name );
+		}
+	},
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations
+	swap: function( elem, options, callback ) {
+		var old = {};
+
+		// Remember the old values, and insert the new ones
+		for ( var name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		callback.call( elem );
+
+		// Revert the old values
+		for ( name in options ) {
+			elem.style[ name ] = old[ name ];
+		}
+	}
+});
+
+// DEPRECATED, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+jQuery.each(["height", "width"], function( i, name ) {
+	jQuery.cssHooks[ name ] = {
+		get: function( elem, computed, extra ) {
+			var val;
+
+			if ( computed ) {
+				if ( elem.offsetWidth !== 0 ) {
+					return getWH( elem, name, extra );
+				} else {
+					jQuery.swap( elem, cssShow, function() {
+						val = getWH( elem, name, extra );
+					});
+				}
+
+				return val;
+			}
+		},
+
+		set: function( elem, value ) {
+			if ( rnumpx.test( value ) ) {
+				// ignore negative width and height values #1599
+				value = parseFloat( value );
+
+				if ( value >= 0 ) {
+					return value + "px";
+				}
+
+			} else {
+				return value;
+			}
+		}
+	};
+});
+
+if ( !jQuery.support.opacity ) {
+	jQuery.cssHooks.opacity = {
+		get: function( elem, computed ) {
+			// IE uses filters for opacity
+			return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+				( parseFloat( RegExp.$1 ) / 100 ) + "" :
+				computed ? "1" : "";
+		},
+
+		set: function( elem, value ) {
+			var style = elem.style,
+				currentStyle = elem.currentStyle,
+				opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+				filter = currentStyle && currentStyle.filter || style.filter || "";
+
+			// IE has trouble with opacity if it does not have layout
+			// Force it by setting the zoom level
+			style.zoom = 1;
+
+			// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+			if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
+
+				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+				// if "filter:" is present at all, clearType is disabled, we want to avoid this
+				// style.removeAttribute is IE Only, but so apparently is this code path...
+				style.removeAttribute( "filter" );
+
+				// if there there is no filter style applied in a css rule, we are done
+				if ( currentStyle && !currentStyle.filter ) {
+					return;
+				}
+			}
+
+			// otherwise, set new filter values
+			style.filter = ralpha.test( filter ) ?
+				filter.replace( ralpha, opacity ) :
+				filter + " " + opacity;
+		}
+	};
+}
+
+jQuery(function() {
+	// This hook cannot be added until DOM ready because the support test
+	// for it is not run until after DOM ready
+	if ( !jQuery.support.reliableMarginRight ) {
+		jQuery.cssHooks.marginRight = {
+			get: function( elem, computed ) {
+				// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+				// Work around by temporarily setting element display to inline-block
+				var ret;
+				jQuery.swap( elem, { "display": "inline-block" }, function() {
+					if ( computed ) {
+						ret = curCSS( elem, "margin-right", "marginRight" );
+					} else {
+						ret = elem.style.marginRight;
+					}
+				});
+				return ret;
+			}
+		};
+	}
+});
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+	getComputedStyle = function( elem, name ) {
+		var ret, defaultView, computedStyle;
+
+		name = name.replace( rupper, "-$1" ).toLowerCase();
+
+		if ( (defaultView = elem.ownerDocument.defaultView) &&
+				(computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+			ret = computedStyle.getPropertyValue( name );
+			if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+				ret = jQuery.style( elem, name );
+			}
+		}
+
+		return ret;
+	};
+}
+
+if ( document.documentElement.currentStyle ) {
+	currentStyle = function( elem, name ) {
+		var left, rsLeft, uncomputed,
+			ret = elem.currentStyle && elem.currentStyle[ name ],
+			style = elem.style;
+
+		// Avoid setting ret to empty string here
+		// so we don't default to auto
+		if ( ret === null && style && (uncomputed = style[ name ]) ) {
+			ret = uncomputed;
+		}
+
+		// From the awesome hack by Dean Edwards
+		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+		// If we're not dealing with a regular pixel number
+		// but a number that has a weird ending, we need to convert it to pixels
+		if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
+
+			// Remember the original values
+			left = style.left;
+			rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+			// Put in the new values to get a computed value out
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = elem.currentStyle.left;
+			}
+			style.left = name === "fontSize" ? "1em" : ( ret || 0 );
+			ret = style.pixelLeft + "px";
+
+			// Revert the changed values
+			style.left = left;
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = rsLeft;
+			}
+		}
+
+		return ret === "" ? "auto" : ret;
+	};
+}
+
+curCSS = getComputedStyle || currentStyle;
+
+function getWH( elem, name, extra ) {
+
+	// Start with offset property
+	var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+		which = name === "width" ? cssWidth : cssHeight,
+		i = 0,
+		len = which.length;
+
+	if ( val > 0 ) {
+		if ( extra !== "border" ) {
+			for ( ; i < len; i++ ) {
+				if ( !extra ) {
+					val -= parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0;
+				}
+				if ( extra === "margin" ) {
+					val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0;
+				} else {
+					val -= parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0;
+				}
+			}
+		}
+
+		return val + "px";
+	}
+
+	// Fall back to computed then uncomputed css if necessary
+	val = curCSS( elem, name, name );
+	if ( val < 0 || val == null ) {
+		val = elem.style[ name ] || 0;
+	}
+	// Normalize "", auto, and prepare for extra
+	val = parseFloat( val ) || 0;
+
+	// Add padding, border, margin
+	if ( extra ) {
+		for ( ; i < len; i++ ) {
+			val += parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0;
+			if ( extra !== "padding" ) {
+				val += parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0;
+			}
+			if ( extra === "margin" ) {
+				val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0;
+			}
+		}
+	}
+
+	return val + "px";
+}
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.hidden = function( elem ) {
+		var width = elem.offsetWidth,
+			height = elem.offsetHeight;
+
+		return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
+	};
+
+	jQuery.expr.filters.visible = function( elem ) {
+		return !jQuery.expr.filters.hidden( elem );
+	};
+}
+
+
+
+
+var r20 = /%20/g,
+	rbracket = /\[\]$/,
+	rCRLF = /\r?\n/g,
+	rhash = /#.*$/,
+	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+	rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+	// #7653, #8125, #8152: local protocol detection
+	rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+	rnoContent = /^(?:GET|HEAD)$/,
+	rprotocol = /^\/\//,
+	rquery = /\?/,
+	rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+	rselectTextarea = /^(?:select|textarea)/i,
+	rspacesAjax = /\s+/,
+	rts = /([?&])_=[^&]*/,
+	rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
+
+	// Keep a copy of the old load method
+	_load = jQuery.fn.load,
+
+	/* Prefilters
+	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+	 * 2) These are called:
+	 *    - BEFORE asking for a transport
+	 *    - AFTER param serialization (s.data is a string if s.processData is true)
+	 * 3) key is the dataType
+	 * 4) the catchall symbol "*" can be used
+	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+	 */
+	prefilters = {},
+
+	/* Transports bindings
+	 * 1) key is the dataType
+	 * 2) the catchall symbol "*" can be used
+	 * 3) selection will start with transport dataType and THEN go to "*" if needed
+	 */
+	transports = {},
+
+	// Document location
+	ajaxLocation,
+
+	// Document location segments
+	ajaxLocParts,
+
+	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+	allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+	ajaxLocation = location.href;
+} catch( e ) {
+	// Use the href attribute of an A element
+	// since IE will modify it given document.location
+	ajaxLocation = document.createElement( "a" );
+	ajaxLocation.href = "";
+	ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+	// dataTypeExpression is optional and defaults to "*"
+	return function( dataTypeExpression, func ) {
+
+		if ( typeof dataTypeExpression !== "string" ) {
+			func = dataTypeExpression;
+			dataTypeExpression = "*";
+		}
+
+		if ( jQuery.isFunction( func ) ) {
+			var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
+				i = 0,
+				length = dataTypes.length,
+				dataType,
+				list,
+				placeBefore;
+
+			// For each dataType in the dataTypeExpression
+			for ( ; i < length; i++ ) {
+				dataType = dataTypes[ i ];
+				// We control if we're asked to add before
+				// any existing element
+				placeBefore = /^\+/.test( dataType );
+				if ( placeBefore ) {
+					dataType = dataType.substr( 1 ) || "*";
+				}
+				list = structure[ dataType ] = structure[ dataType ] || [];
+				// then we add to the structure accordingly
+				list[ placeBefore ? "unshift" : "push" ]( func );
+			}
+		}
+	};
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+		dataType /* internal */, inspected /* internal */ ) {
+
+	dataType = dataType || options.dataTypes[ 0 ];
+	inspected = inspected || {};
+
+	inspected[ dataType ] = true;
+
+	var list = structure[ dataType ],
+		i = 0,
+		length = list ? list.length : 0,
+		executeOnly = ( structure === prefilters ),
+		selection;
+
+	for ( ; i < length && ( executeOnly || !selection ); i++ ) {
+		selection = list[ i ]( options, originalOptions, jqXHR );
+		// If we got redirected to another dataType
+		// we try there if executing only and not done already
+		if ( typeof selection === "string" ) {
+			if ( !executeOnly || inspected[ selection ] ) {
+				selection = undefined;
+			} else {
+				options.dataTypes.unshift( selection );
+				selection = inspectPrefiltersOrTransports(
+						structure, options, originalOptions, jqXHR, selection, inspected );
+			}
+		}
+	}
+	// If we're only executing or nothing was selected
+	// we try the catchall dataType if not done already
+	if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+		selection = inspectPrefiltersOrTransports(
+				structure, options, originalOptions, jqXHR, "*", inspected );
+	}
+	// unnecessary when only executing (prefilters)
+	// but it'll be ignored by the caller in that case
+	return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+	var key, deep,
+		flatOptions = jQuery.ajaxSettings.flatOptions || {};
+	for ( key in src ) {
+		if ( src[ key ] !== undefined ) {
+			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+		}
+	}
+	if ( deep ) {
+		jQuery.extend( true, target, deep );
+	}
+}
+
+jQuery.fn.extend({
+	load: function( url, params, callback ) {
+		if ( typeof url !== "string" && _load ) {
+			return _load.apply( this, arguments );
+
+		// Don't do a request if no elements are being requested
+		} else if ( !this.length ) {
+			return this;
+		}
+
+		var off = url.indexOf( " " );
+		if ( off >= 0 ) {
+			var selector = url.slice( off, url.length );
+			url = url.slice( 0, off );
+		}
+
+		// Default to a GET request
+		var type = "GET";
+
+		// If the second parameter was provided
+		if ( params ) {
+			// If it's a function
+			if ( jQuery.isFunction( params ) ) {
+				// We assume that it's the callback
+				callback = params;
+				params = undefined;
+
+			// Otherwise, build a param string
+			} else if ( typeof params === "object" ) {
+				params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+				type = "POST";
+			}
+		}
+
+		var self = this;
+
+		// Request the remote document
+		jQuery.ajax({
+			url: url,
+			type: type,
+			dataType: "html",
+			data: params,
+			// Complete callback (responseText is used internally)
+			complete: function( jqXHR, status, responseText ) {
+				// Store the response as specified by the jqXHR object
+				responseText = jqXHR.responseText;
+				// If successful, inject the HTML into all the matched elements
+				if ( jqXHR.isResolved() ) {
+					// #4825: Get the actual response in case
+					// a dataFilter is present in ajaxSettings
+					jqXHR.done(function( r ) {
+						responseText = r;
+					});
+					// See if a selector was specified
+					self.html( selector ?
+						// Create a dummy div to hold the results
+						jQuery("<div>")
+							// inject the contents of the document in, removing the scripts
+							// to avoid any 'Permission Denied' errors in IE
+							.append(responseText.replace(rscript, ""))
+
+							// Locate the specified elements
+							.find(selector) :
+
+						// If not, just inject the full result
+						responseText );
+				}
+
+				if ( callback ) {
+					self.each( callback, [ responseText, status, jqXHR ] );
+				}
+			}
+		});
+
+		return this;
+	},
+
+	serialize: function() {
+		return jQuery.param( this.serializeArray() );
+	},
+
+	serializeArray: function() {
+		return this.map(function(){
+			return this.elements ? jQuery.makeArray( this.elements ) : this;
+		})
+		.filter(function(){
+			return this.name && !this.disabled &&
+				( this.checked || rselectTextarea.test( this.nodeName ) ||
+					rinput.test( this.type ) );
+		})
+		.map(function( i, elem ){
+			var val = jQuery( this ).val();
+
+			return val == null ?
+				null :
+				jQuery.isArray( val ) ?
+					jQuery.map( val, function( val, i ){
+						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+					}) :
+					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+		}).get();
+	}
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+	jQuery.fn[ o ] = function( f ){
+		return this.on( o, f );
+	};
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+	jQuery[ method ] = function( url, data, callback, type ) {
+		// shift arguments if data argument was omitted
+		if ( jQuery.isFunction( data ) ) {
+			type = type || callback;
+			callback = data;
+			data = undefined;
+		}
+
+		return jQuery.ajax({
+			type: method,
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	};
+});
+
+jQuery.extend({
+
+	getScript: function( url, callback ) {
+		return jQuery.get( url, undefined, callback, "script" );
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get( url, data, callback, "json" );
+	},
+
+	// Creates a full fledged settings object into target
+	// with both ajaxSettings and settings fields.
+	// If target is omitted, writes into ajaxSettings.
+	ajaxSetup: function( target, settings ) {
+		if ( settings ) {
+			// Building a settings object
+			ajaxExtend( target, jQuery.ajaxSettings );
+		} else {
+			// Extending ajaxSettings
+			settings = target;
+			target = jQuery.ajaxSettings;
+		}
+		ajaxExtend( target, settings );
+		return target;
+	},
+
+	ajaxSettings: {
+		url: ajaxLocation,
+		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+		global: true,
+		type: "GET",
+		contentType: "application/x-www-form-urlencoded",
+		processData: true,
+		async: true,
+		/*
+		timeout: 0,
+		data: null,
+		dataType: null,
+		username: null,
+		password: null,
+		cache: null,
+		traditional: false,
+		headers: {},
+		*/
+
+		accepts: {
+			xml: "application/xml, text/xml",
+			html: "text/html",
+			text: "text/plain",
+			json: "application/json, text/javascript",
+			"*": allTypes
+		},
+
+		contents: {
+			xml: /xml/,
+			html: /html/,
+			json: /json/
+		},
+
+		responseFields: {
+			xml: "responseXML",
+			text: "responseText"
+		},
+
+		// List of data converters
+		// 1) key format is "source_type destination_type" (a single space in-between)
+		// 2) the catchall symbol "*" can be used for source_type
+		converters: {
+
+			// Convert anything to text
+			"* text": window.String,
+
+			// Text to html (true = no transformation)
+			"text html": true,
+
+			// Evaluate text as a json expression
+			"text json": jQuery.parseJSON,
+
+			// Parse text as xml
+			"text xml": jQuery.parseXML
+		},
+
+		// For options that shouldn't be deep extended:
+		// you can add your own custom options here if
+		// and when you create one that shouldn't be
+		// deep extended (see ajaxExtend)
+		flatOptions: {
+			context: true,
+			url: true
+		}
+	},
+
+	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+	ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+	// Main method
+	ajax: function( url, options ) {
+
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
+			options = url;
+			url = undefined;
+		}
+
+		// Force options to be an object
+		options = options || {};
+
+		var // Create the final options object
+			s = jQuery.ajaxSetup( {}, options ),
+			// Callbacks context
+			callbackContext = s.context || s,
+			// Context for global events
+			// It's the callbackContext if one was provided in the options
+			// and if it's a DOM node or a jQuery collection
+			globalEventContext = callbackContext !== s &&
+				( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+						jQuery( callbackContext ) : jQuery.event,
+			// Deferreds
+			deferred = jQuery.Deferred(),
+			completeDeferred = jQuery.Callbacks( "once memory" ),
+			// Status-dependent callbacks
+			statusCode = s.statusCode || {},
+			// ifModified key
+			ifModifiedKey,
+			// Headers (they are sent all at once)
+			requestHeaders = {},
+			requestHeadersNames = {},
+			// Response headers
+			responseHeadersString,
+			responseHeaders,
+			// transport
+			transport,
+			// timeout handle
+			timeoutTimer,
+			// Cross-domain detection vars
+			parts,
+			// The jqXHR state
+			state = 0,
+			// To know if global events are to be dispatched
+			fireGlobals,
+			// Loop variable
+			i,
+			// Fake xhr
+			jqXHR = {
+
+				readyState: 0,
+
+				// Caches the header
+				setRequestHeader: function( name, value ) {
+					if ( !state ) {
+						var lname = name.toLowerCase();
+						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+						requestHeaders[ name ] = value;
+					}
+					return this;
+				},
+
+				// Raw string
+				getAllResponseHeaders: function() {
+					return state === 2 ? responseHeadersString : null;
+				},
+
+				// Builds headers hashtable if needed
+				getResponseHeader: function( key ) {
+					var match;
+					if ( state === 2 ) {
+						if ( !responseHeaders ) {
+							responseHeaders = {};
+							while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+							}
+						}
+						match = responseHeaders[ key.toLowerCase() ];
+					}
+					return match === undefined ? null : match;
+				},
+
+				// Overrides response content-type header
+				overrideMimeType: function( type ) {
+					if ( !state ) {
+						s.mimeType = type;
+					}
+					return this;
+				},
+
+				// Cancel the request
+				abort: function( statusText ) {
+					statusText = statusText || "abort";
+					if ( transport ) {
+						transport.abort( statusText );
+					}
+					done( 0, statusText );
+					return this;
+				}
+			};
+
+		// Callback for when everything is done
+		// It is defined here because jslint complains if it is declared
+		// at the end of the function (which would be more logical and readable)
+		function done( status, nativeStatusText, responses, headers ) {
+
+			// Called once
+			if ( state === 2 ) {
+				return;
+			}
+
+			// State is "done" now
+			state = 2;
+
+			// Clear timeout if it exists
+			if ( timeoutTimer ) {
+				clearTimeout( timeoutTimer );
+			}
+
+			// Dereference transport for early garbage collection
+			// (no matter how long the jqXHR object will be used)
+			transport = undefined;
+
+			// Cache response headers
+			responseHeadersString = headers || "";
+
+			// Set readyState
+			jqXHR.readyState = status > 0 ? 4 : 0;
+
+			var isSuccess,
+				success,
+				error,
+				statusText = nativeStatusText,
+				response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
+				lastModified,
+				etag;
+
+			// If successful, handle type chaining
+			if ( status >= 200 && status < 300 || status === 304 ) {
+
+				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+				if ( s.ifModified ) {
+
+					if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
+						jQuery.lastModified[ ifModifiedKey ] = lastModified;
+					}
+					if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
+						jQuery.etag[ ifModifiedKey ] = etag;
+					}
+				}
+
+				// If not modified
+				if ( status === 304 ) {
+
+					statusText = "notmodified";
+					isSuccess = true;
+
+				// If we have data
+				} else {
+
+					try {
+						success = ajaxConvert( s, response );
+						statusText = "success";
+						isSuccess = true;
+					} catch(e) {
+						// We have a parsererror
+						statusText = "parsererror";
+						error = e;
+					}
+				}
+			} else {
+				// We extract error from statusText
+				// then normalize statusText and status for non-aborts
+				error = statusText;
+				if ( !statusText || status ) {
+					statusText = "error";
+					if ( status < 0 ) {
+						status = 0;
+					}
+				}
+			}
+
+			// Set data for the fake xhr object
+			jqXHR.status = status;
+			jqXHR.statusText = "" + ( nativeStatusText || statusText );
+
+			// Success/Error
+			if ( isSuccess ) {
+				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+			} else {
+				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+			}
+
+			// Status-dependent callbacks
+			jqXHR.statusCode( statusCode );
+			statusCode = undefined;
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+						[ jqXHR, s, isSuccess ? success : error ] );
+			}
+
+			// Complete
+			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+				// Handle the global AJAX counter
+				if ( !( --jQuery.active ) ) {
+					jQuery.event.trigger( "ajaxStop" );
+				}
+			}
+		}
+
+		// Attach deferreds
+		deferred.promise( jqXHR );
+		jqXHR.success = jqXHR.done;
+		jqXHR.error = jqXHR.fail;
+		jqXHR.complete = completeDeferred.add;
+
+		// Status-dependent callbacks
+		jqXHR.statusCode = function( map ) {
+			if ( map ) {
+				var tmp;
+				if ( state < 2 ) {
+					for ( tmp in map ) {
+						statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+					}
+				} else {
+					tmp = map[ jqXHR.status ];
+					jqXHR.then( tmp, tmp );
+				}
+			}
+			return this;
+		};
+
+		// Remove hash character (#7531: and string promotion)
+		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+		// We also use the url parameter if available
+		s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+		// Extract dataTypes list
+		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+
+		// Determine if a cross-domain request is in order
+		if ( s.crossDomain == null ) {
+			parts = rurl.exec( s.url.toLowerCase() );
+			s.crossDomain = !!( parts &&
+				( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+			);
+		}
+
+		// Convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" ) {
+			s.data = jQuery.param( s.data, s.traditional );
+		}
+
+		// Apply prefilters
+		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+		// If request was aborted inside a prefiler, stop there
+		if ( state === 2 ) {
+			return false;
+		}
+
+		// We can fire global events as of now if asked to
+		fireGlobals = s.global;
+
+		// Uppercase the type
+		s.type = s.type.toUpperCase();
+
+		// Determine if request has content
+		s.hasContent = !rnoContent.test( s.type );
+
+		// Watch for a new set of requests
+		if ( fireGlobals && jQuery.active++ === 0 ) {
+			jQuery.event.trigger( "ajaxStart" );
+		}
+
+		// More options handling for requests with no content
+		if ( !s.hasContent ) {
+
+			// If data is available, append data to url
+			if ( s.data ) {
+				s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+				// #9682: remove data so that it's not used in an eventual retry
+				delete s.data;
+			}
+
+			// Get ifModifiedKey before adding the anti-cache parameter
+			ifModifiedKey = s.url;
+
+			// Add anti-cache in url if needed
+			if ( s.cache === false ) {
+
+				var ts = jQuery.now(),
+					// try replacing _= if it is there
+					ret = s.url.replace( rts, "$1_=" + ts );
+
+				// if nothing was replaced, add timestamp to the end
+				s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+			}
+		}
+
+		// Set the correct header, if data is being sent
+		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+			jqXHR.setRequestHeader( "Content-Type", s.contentType );
+		}
+
+		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+		if ( s.ifModified ) {
+			ifModifiedKey = ifModifiedKey || s.url;
+			if ( jQuery.lastModified[ ifModifiedKey ] ) {
+				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+			}
+			if ( jQuery.etag[ ifModifiedKey ] ) {
+				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+			}
+		}
+
+		// Set the Accepts header for the server, depending on the dataType
+		jqXHR.setRequestHeader(
+			"Accept",
+			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+				s.accepts[ "*" ]
+		);
+
+		// Check for headers option
+		for ( i in s.headers ) {
+			jqXHR.setRequestHeader( i, s.headers[ i ] );
+		}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+				// Abort if not done already
+				jqXHR.abort();
+				return false;
+
+		}
+
+		// Install callbacks on deferreds
+		for ( i in { success: 1, error: 1, complete: 1 } ) {
+			jqXHR[ i ]( s[ i ] );
+		}
+
+		// Get transport
+		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+		// If no transport, we auto-abort
+		if ( !transport ) {
+			done( -1, "No Transport" );
+		} else {
+			jqXHR.readyState = 1;
+			// Send global event
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+			}
+			// Timeout
+			if ( s.async && s.timeout > 0 ) {
+				timeoutTimer = setTimeout( function(){
+					jqXHR.abort( "timeout" );
+				}, s.timeout );
+			}
+
+			try {
+				state = 1;
+				transport.send( requestHeaders, done );
+			} catch (e) {
+				// Propagate exception as error if not done
+				if ( state < 2 ) {
+					done( -1, e );
+				// Simply rethrow otherwise
+				} else {
+					throw e;
+				}
+			}
+		}
+
+		return jqXHR;
+	},
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	param: function( a, traditional ) {
+		var s = [],
+			add = function( key, value ) {
+				// If value is a function, invoke it and return its value
+				value = jQuery.isFunction( value ) ? value() : value;
+				s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+			};
+
+		// Set traditional to true for jQuery <= 1.3.2 behavior.
+		if ( traditional === undefined ) {
+			traditional = jQuery.ajaxSettings.traditional;
+		}
+
+		// If an array was passed in, assume that it is an array of form elements.
+		if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+			// Serialize the form elements
+			jQuery.each( a, function() {
+				add( this.name, this.value );
+			});
+
+		} else {
+			// If traditional, encode the "old" way (the way 1.3.2 or older
+			// did it), otherwise encode params recursively.
+			for ( var prefix in a ) {
+				buildParams( prefix, a[ prefix ], traditional, add );
+			}
+		}
+
+		// Return the resulting serialization
+		return s.join( "&" ).replace( r20, "+" );
+	}
+});
+
+function buildParams( prefix, obj, traditional, add ) {
+	if ( jQuery.isArray( obj ) ) {
+		// Serialize array item.
+		jQuery.each( obj, function( i, v ) {
+			if ( traditional || rbracket.test( prefix ) ) {
+				// Treat each array item as a scalar.
+				add( prefix, v );
+
+			} else {
+				// If array item is non-scalar (array or object), encode its
+				// numeric index to resolve deserialization ambiguity issues.
+				// Note that rack (as of 1.0.0) can't currently deserialize
+				// nested arrays properly, and attempting to do so may cause
+				// a server error. Possible fixes are to modify rack's
+				// deserialization algorithm or to provide an option or flag
+				// to force array serialization to be shallow.
+				buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add );
+			}
+		});
+
+	} else if ( !traditional && obj != null && typeof obj === "object" ) {
+		// Serialize object item.
+		for ( var name in obj ) {
+			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+		}
+
+	} else {
+		// Serialize scalar item.
+		add( prefix, obj );
+	}
+}
+
+// This is still on the jQuery object... for now
+// Want to move this to jQuery.ajax some day
+jQuery.extend({
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+	etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+	var contents = s.contents,
+		dataTypes = s.dataTypes,
+		responseFields = s.responseFields,
+		ct,
+		type,
+		finalDataType,
+		firstDataType;
+
+	// Fill responseXXX fields
+	for ( type in responseFields ) {
+		if ( type in responses ) {
+			jqXHR[ responseFields[type] ] = responses[ type ];
+		}
+	}
+
+	// Remove auto dataType and get content-type in the process
+	while( dataTypes[ 0 ] === "*" ) {
+		dataTypes.shift();
+		if ( ct === undefined ) {
+			ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+		}
+	}
+
+	// Check if we're dealing with a known content-type
+	if ( ct ) {
+		for ( type in contents ) {
+			if ( contents[ type ] && contents[ type ].test( ct ) ) {
+				dataTypes.unshift( type );
+				break;
+			}
+		}
+	}
+
+	// Check to see if we have a response for the expected dataType
+	if ( dataTypes[ 0 ] in responses ) {
+		finalDataType = dataTypes[ 0 ];
+	} else {
+		// Try convertible dataTypes
+		for ( type in responses ) {
+			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+				finalDataType = type;
+				break;
+			}
+			if ( !firstDataType ) {
+				firstDataType = type;
+			}
+		}
+		// Or just use first one
+		finalDataType = finalDataType || firstDataType;
+	}
+
+	// If we found a dataType
+	// We add the dataType to the list if needed
+	// and return the corresponding response
+	if ( finalDataType ) {
+		if ( finalDataType !== dataTypes[ 0 ] ) {
+			dataTypes.unshift( finalDataType );
+		}
+		return responses[ finalDataType ];
+	}
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+	// Apply the dataFilter if provided
+	if ( s.dataFilter ) {
+		response = s.dataFilter( response, s.dataType );
+	}
+
+	var dataTypes = s.dataTypes,
+		converters = {},
+		i,
+		key,
+		length = dataTypes.length,
+		tmp,
+		// Current and previous dataTypes
+		current = dataTypes[ 0 ],
+		prev,
+		// Conversion expression
+		conversion,
+		// Conversion function
+		conv,
+		// Conversion functions (transitive conversion)
+		conv1,
+		conv2;
+
+	// For each dataType in the chain
+	for ( i = 1; i < length; i++ ) {
+
+		// Create converters map
+		// with lowercased keys
+		if ( i === 1 ) {
+			for ( key in s.converters ) {
+				if ( typeof key === "string" ) {
+					converters[ key.toLowerCase() ] = s.converters[ key ];
+				}
+			}
+		}
+
+		// Get the dataTypes
+		prev = current;
+		current = dataTypes[ i ];
+
+		// If current is auto dataType, update it to prev
+		if ( current === "*" ) {
+			current = prev;
+		// If no auto and dataTypes are actually different
+		} else if ( prev !== "*" && prev !== current ) {
+
+			// Get the converter
+			conversion = prev + " " + current;
+			conv = converters[ conversion ] || converters[ "* " + current ];
+
+			// If there is no direct converter, search transitively
+			if ( !conv ) {
+				conv2 = undefined;
+				for ( conv1 in converters ) {
+					tmp = conv1.split( " " );
+					if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
+						conv2 = converters[ tmp[1] + " " + current ];
+						if ( conv2 ) {
+							conv1 = converters[ conv1 ];
+							if ( conv1 === true ) {
+								conv = conv2;
+							} else if ( conv2 === true ) {
+								conv = conv1;
+							}
+							break;
+						}
+					}
+				}
+			}
+			// If we found no converter, dispatch an error
+			if ( !( conv || conv2 ) ) {
+				jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
+			}
+			// If found converter is not an equivalence
+			if ( conv !== true ) {
+				// Convert with 1 or 2 converters accordingly
+				response = conv ? conv( response ) : conv2( conv1(response) );
+			}
+		}
+	}
+	return response;
+}
+
+
+
+
+var jsc = jQuery.now(),
+	jsre = /(\=)\?(&|$)|\?\?/i;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+	jsonp: "callback",
+	jsonpCallback: function() {
+		return jQuery.expando + "_" + ( jsc++ );
+	}
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+	var inspectData = s.contentType === "application/x-www-form-urlencoded" &&
+		( typeof s.data === "string" );
+
+	if ( s.dataTypes[ 0 ] === "jsonp" ||
+		s.jsonp !== false && ( jsre.test( s.url ) ||
+				inspectData && jsre.test( s.data ) ) ) {
+
+		var responseContainer,
+			jsonpCallback = s.jsonpCallback =
+				jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
+			previous = window[ jsonpCallback ],
+			url = s.url,
+			data = s.data,
+			replace = "$1" + jsonpCallback + "$2";
+
+		if ( s.jsonp !== false ) {
+			url = url.replace( jsre, replace );
+			if ( s.url === url ) {
+				if ( inspectData ) {
+					data = data.replace( jsre, replace );
+				}
+				if ( s.data === data ) {
+					// Add callback manually
+					url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
+				}
+			}
+		}
+
+		s.url = url;
+		s.data = data;
+
+		// Install callback
+		window[ jsonpCallback ] = function( response ) {
+			responseContainer = [ response ];
+		};
+
+		// Clean-up function
+		jqXHR.always(function() {
+			// Set callback back to previous value
+			window[ jsonpCallback ] = previous;
+			// Call if it was a function and we have a response
+			if ( responseContainer && jQuery.isFunction( previous ) ) {
+				window[ jsonpCallback ]( responseContainer[ 0 ] );
+			}
+		});
+
+		// Use data converter to retrieve json after script execution
+		s.converters["script json"] = function() {
+			if ( !responseContainer ) {
+				jQuery.error( jsonpCallback + " was not called" );
+			}
+			return responseContainer[ 0 ];
+		};
+
+		// force json dataType
+		s.dataTypes[ 0 ] = "json";
+
+		// Delegate to script
+		return "script";
+	}
+});
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+	accepts: {
+		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+	},
+	contents: {
+		script: /javascript|ecmascript/
+	},
+	converters: {
+		"text script": function( text ) {
+			jQuery.globalEval( text );
+			return text;
+		}
+	}
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+	if ( s.cache === undefined ) {
+		s.cache = false;
+	}
+	if ( s.crossDomain ) {
+		s.type = "GET";
+		s.global = false;
+	}
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+	// This transport only deals with cross domain requests
+	if ( s.crossDomain ) {
+
+		var script,
+			head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+		return {
+
+			send: function( _, callback ) {
+
+				script = document.createElement( "script" );
+
+				script.async = "async";
+
+				if ( s.scriptCharset ) {
+					script.charset = s.scriptCharset;
+				}
+
+				script.src = s.url;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+						// Handle memory leak in IE
+						script.onload = script.onreadystatechange = null;
+
+						// Remove the script
+						if ( head && script.parentNode ) {
+							head.removeChild( script );
+						}
+
+						// Dereference the script
+						script = undefined;
+
+						// Callback if not abort
+						if ( !isAbort ) {
+							callback( 200, "success" );
+						}
+					}
+				};
+				// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
+				// This arises when a base node is used (#2709 and #4378).
+				head.insertBefore( script, head.firstChild );
+			},
+
+			abort: function() {
+				if ( script ) {
+					script.onload( 0, 1 );
+				}
+			}
+		};
+	}
+});
+
+
+
+
+var // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+	xhrOnUnloadAbort = window.ActiveXObject ? function() {
+		// Abort all pending requests
+		for ( var key in xhrCallbacks ) {
+			xhrCallbacks[ key ]( 0, 1 );
+		}
+	} : false,
+	xhrId = 0,
+	xhrCallbacks;
+
+// Functions to create xhrs
+function createStandardXHR() {
+	try {
+		return new window.XMLHttpRequest();
+	} catch( e ) {}
+}
+
+function createActiveXHR() {
+	try {
+		return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+	} catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+	/* Microsoft failed to properly
+	 * implement the XMLHttpRequest in IE7 (can't request local files),
+	 * so we use the ActiveXObject when it is available
+	 * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+	 * we need a fallback.
+	 */
+	function() {
+		return !this.isLocal && createStandardXHR() || createActiveXHR();
+	} :
+	// For all other browsers, use the standard XMLHttpRequest object
+	createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+	jQuery.extend( jQuery.support, {
+		ajax: !!xhr,
+		cors: !!xhr && ( "withCredentials" in xhr )
+	});
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+	jQuery.ajaxTransport(function( s ) {
+		// Cross domain only allowed if supported through XMLHttpRequest
+		if ( !s.crossDomain || jQuery.support.cors ) {
+
+			var callback;
+
+			return {
+				send: function( headers, complete ) {
+
+					// Get a new xhr
+					var xhr = s.xhr(),
+						handle,
+						i;
+
+					// Open the socket
+					// Passing null username, generates a login popup on Opera (#2865)
+					if ( s.username ) {
+						xhr.open( s.type, s.url, s.async, s.username, s.password );
+					} else {
+						xhr.open( s.type, s.url, s.async );
+					}
+
+					// Apply custom fields if provided
+					if ( s.xhrFields ) {
+						for ( i in s.xhrFields ) {
+							xhr[ i ] = s.xhrFields[ i ];
+						}
+					}
+
+					// Override mime type if needed
+					if ( s.mimeType && xhr.overrideMimeType ) {
+						xhr.overrideMimeType( s.mimeType );
+					}
+
+					// X-Requested-With header
+					// For cross-domain requests, seeing as conditions for a preflight are
+					// akin to a jigsaw puzzle, we simply never set it to be sure.
+					// (it can always be set on a per-request basis or even using ajaxSetup)
+					// For same-domain requests, won't change header if already provided.
+					if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+						headers[ "X-Requested-With" ] = "XMLHttpRequest";
+					}
+
+					// Need an extra try/catch for cross domain requests in Firefox 3
+					try {
+						for ( i in headers ) {
+							xhr.setRequestHeader( i, headers[ i ] );
+						}
+					} catch( _ ) {}
+
+					// Do send the request
+					// This may raise an exception which is actually
+					// handled in jQuery.ajax (so no try/catch here)
+					xhr.send( ( s.hasContent && s.data ) || null );
+
+					// Listener
+					callback = function( _, isAbort ) {
+
+						var status,
+							statusText,
+							responseHeaders,
+							responses,
+							xml;
+
+						// Firefox throws exceptions when accessing properties
+						// of an xhr when a network error occured
+						// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+						try {
+
+							// Was never called and is aborted or complete
+							if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+								// Only called once
+								callback = undefined;
+
+								// Do not keep as active anymore
+								if ( handle ) {
+									xhr.onreadystatechange = jQuery.noop;
+									if ( xhrOnUnloadAbort ) {
+										delete xhrCallbacks[ handle ];
+									}
+								}
+
+								// If it's an abort
+								if ( isAbort ) {
+									// Abort it manually if needed
+									if ( xhr.readyState !== 4 ) {
+										xhr.abort();
+									}
+								} else {
+									status = xhr.status;
+									responseHeaders = xhr.getAllResponseHeaders();
+									responses = {};
+									xml = xhr.responseXML;
+
+									// Construct response list
+									if ( xml && xml.documentElement /* #4958 */ ) {
+										responses.xml = xml;
+									}
+									responses.text = xhr.responseText;
+
+									// Firefox throws an exception when accessing
+									// statusText for faulty cross-domain requests
+									try {
+										statusText = xhr.statusText;
+									} catch( e ) {
+										// We normalize with Webkit giving an empty statusText
+										statusText = "";
+									}
+
+									// Filter status for non standard behaviors
+
+									// If the request is local and we have data: assume a success
+									// (success with no data won't get notified, that's the best we
+									// can do given current implementations)
+									if ( !status && s.isLocal && !s.crossDomain ) {
+										status = responses.text ? 200 : 404;
+									// IE - #1450: sometimes returns 1223 when it should be 204
+									} else if ( status === 1223 ) {
+										status = 204;
+									}
+								}
+							}
+						} catch( firefoxAccessException ) {
+							if ( !isAbort ) {
+								complete( -1, firefoxAccessException );
+							}
+						}
+
+						// Call complete if needed
+						if ( responses ) {
+							complete( status, statusText, responses, responseHeaders );
+						}
+					};
+
+					// if we're in sync mode or it's in cache
+					// and has been retrieved directly (IE6 & IE7)
+					// we need to manually fire the callback
+					if ( !s.async || xhr.readyState === 4 ) {
+						callback();
+					} else {
+						handle = ++xhrId;
+						if ( xhrOnUnloadAbort ) {
+							// Create the active xhrs callbacks list if needed
+							// and attach the unload handler
+							if ( !xhrCallbacks ) {
+								xhrCallbacks = {};
+								jQuery( window ).unload( xhrOnUnloadAbort );
+							}
+							// Add to list of active xhrs callbacks
+							xhrCallbacks[ handle ] = callback;
+						}
+						xhr.onreadystatechange = callback;
+					}
+				},
+
+				abort: function() {
+					if ( callback ) {
+						callback(0,1);
+					}
+				}
+			};
+		}
+	});
+}
+
+
+
+
+var elemdisplay = {},
+	iframe, iframeDoc,
+	rfxtypes = /^(?:toggle|show|hide)$/,
+	rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
+	timerId,
+	fxAttrs = [
+		// height animations
+		[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+		// width animations
+		[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+		// opacity animations
+		[ "opacity" ]
+	],
+	fxNow;
+
+jQuery.fn.extend({
+	show: function( speed, easing, callback ) {
+		var elem, display;
+
+		if ( speed || speed === 0 ) {
+			return this.animate( genFx("show", 3), speed, easing, callback );
+
+		} else {
+			for ( var i = 0, j = this.length; i < j; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.style ) {
+					display = elem.style.display;
+
+					// Reset the inline display of this element to learn if it is
+					// being hidden by cascaded rules or not
+					if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
+						display = elem.style.display = "";
+					}
+
+					// Set elements which have been overridden with display: none
+					// in a stylesheet to whatever the default browser style is
+					// for such an element
+					if ( display === "" && jQuery.css(elem, "display") === "none" ) {
+						jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
+					}
+				}
+			}
+
+			// Set the display of most of the elements in a second loop
+			// to avoid the constant reflow
+			for ( i = 0; i < j; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.style ) {
+					display = elem.style.display;
+
+					if ( display === "" || display === "none" ) {
+						elem.style.display = jQuery._data( elem, "olddisplay" ) || "";
+					}
+				}
+			}
+
+			return this;
+		}
+	},
+
+	hide: function( speed, easing, callback ) {
+		if ( speed || speed === 0 ) {
+			return this.animate( genFx("hide", 3), speed, easing, callback);
+
+		} else {
+			var elem, display,
+				i = 0,
+				j = this.length;
+
+			for ( ; i < j; i++ ) {
+				elem = this[i];
+				if ( elem.style ) {
+					display = jQuery.css( elem, "display" );
+
+					if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) {
+						jQuery._data( elem, "olddisplay", display );
+					}
+				}
+			}
+
+			// Set the display of the elements in a second loop
+			// to avoid the constant reflow
+			for ( i = 0; i < j; i++ ) {
+				if ( this[i].style ) {
+					this[i].style.display = "none";
+				}
+			}
+
+			return this;
+		}
+	},
+
+	// Save the old toggle function
+	_toggle: jQuery.fn.toggle,
+
+	toggle: function( fn, fn2, callback ) {
+		var bool = typeof fn === "boolean";
+
+		if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+			this._toggle.apply( this, arguments );
+
+		} else if ( fn == null || bool ) {
+			this.each(function() {
+				var state = bool ? fn : jQuery(this).is(":hidden");
+				jQuery(this)[ state ? "show" : "hide" ]();
+			});
+
+		} else {
+			this.animate(genFx("toggle", 3), fn, fn2, callback);
+		}
+
+		return this;
+	},
+
+	fadeTo: function( speed, to, easing, callback ) {
+		return this.filter(":hidden").css("opacity", 0).show().end()
+					.animate({opacity: to}, speed, easing, callback);
+	},
+
+	animate: function( prop, speed, easing, callback ) {
+		var optall = jQuery.speed( speed, easing, callback );
+
+		if ( jQuery.isEmptyObject( prop ) ) {
+			return this.each( optall.complete, [ false ] );
+		}
+
+		// Do not change referenced properties as per-property easing will be lost
+		prop = jQuery.extend( {}, prop );
+
+		function doAnimation() {
+			// XXX 'this' does not always have a nodeName when running the
+			// test suite
+
+			if ( optall.queue === false ) {
+				jQuery._mark( this );
+			}
+
+			var opt = jQuery.extend( {}, optall ),
+				isElement = this.nodeType === 1,
+				hidden = isElement && jQuery(this).is(":hidden"),
+				name, val, p, e,
+				parts, start, end, unit,
+				method;
+
+			// will store per property easing and be used to determine when an animation is complete
+			opt.animatedProperties = {};
+
+			for ( p in prop ) {
+
+				// property name normalization
+				name = jQuery.camelCase( p );
+				if ( p !== name ) {
+					prop[ name ] = prop[ p ];
+					delete prop[ p ];
+				}
+
+				val = prop[ name ];
+
+				// easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
+				if ( jQuery.isArray( val ) ) {
+					opt.animatedProperties[ name ] = val[ 1 ];
+					val = prop[ name ] = val[ 0 ];
+				} else {
+					opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
+				}
+
+				if ( val === "hide" && hidden || val === "show" && !hidden ) {
+					return opt.complete.call( this );
+				}
+
+				if ( isElement && ( name === "height" || name === "width" ) ) {
+					// Make sure that nothing sneaks out
+					// Record all 3 overflow attributes because IE does not
+					// change the overflow attribute when overflowX and
+					// overflowY are set to the same value
+					opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
+
+					// Set display property to inline-block for height/width
+					// animations on inline elements that are having width/height animated
+					if ( jQuery.css( this, "display" ) === "inline" &&
+							jQuery.css( this, "float" ) === "none" ) {
+
+						// inline-level elements accept inline-block;
+						// block-level elements need to be inline with layout
+						if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
+							this.style.display = "inline-block";
+
+						} else {
+							this.style.zoom = 1;
+						}
+					}
+				}
+			}
+
+			if ( opt.overflow != null ) {
+				this.style.overflow = "hidden";
+			}
+
+			for ( p in prop ) {
+				e = new jQuery.fx( this, opt, p );
+				val = prop[ p ];
+
+				if ( rfxtypes.test( val ) ) {
+
+					// Tracks whether to show or hide based on private
+					// data attached to the element
+					method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
+					if ( method ) {
+						jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
+						e[ method ]();
+					} else {
+						e[ val ]();
+					}
+
+				} else {
+					parts = rfxnum.exec( val );
+					start = e.cur();
+
+					if ( parts ) {
+						end = parseFloat( parts[2] );
+						unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
+
+						// We need to compute starting value
+						if ( unit !== "px" ) {
+							jQuery.style( this, p, (end || 1) + unit);
+							start = ( (end || 1) / e.cur() ) * start;
+							jQuery.style( this, p, start + unit);
+						}
+
+						// If a +=/-= token was provided, we're doing a relative animation
+						if ( parts[1] ) {
+							end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
+						}
+
+						e.custom( start, end, unit );
+
+					} else {
+						e.custom( start, val, "" );
+					}
+				}
+			}
+
+			// For JS strict compliance
+			return true;
+		}
+
+		return optall.queue === false ?
+			this.each( doAnimation ) :
+			this.queue( optall.queue, doAnimation );
+	},
+
+	stop: function( type, clearQueue, gotoEnd ) {
+		if ( typeof type !== "string" ) {
+			gotoEnd = clearQueue;
+			clearQueue = type;
+			type = undefined;
+		}
+		if ( clearQueue && type !== false ) {
+			this.queue( type || "fx", [] );
+		}
+
+		return this.each(function() {
+			var index,
+				hadTimers = false,
+				timers = jQuery.timers,
+				data = jQuery._data( this );
+
+			// clear marker counters if we know they won't be
+			if ( !gotoEnd ) {
+				jQuery._unmark( true, this );
+			}
+
+			function stopQueue( elem, data, index ) {
+				var hooks = data[ index ];
+				jQuery.removeData( elem, index, true );
+				hooks.stop( gotoEnd );
+			}
+
+			if ( type == null ) {
+				for ( index in data ) {
+					if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) {
+						stopQueue( this, data, index );
+					}
+				}
+			} else if ( data[ index = type + ".run" ] && data[ index ].stop ){
+				stopQueue( this, data, index );
+			}
+
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+					if ( gotoEnd ) {
+
+						// force the next step to be the last
+						timers[ index ]( true );
+					} else {
+						timers[ index ].saveState();
+					}
+					hadTimers = true;
+					timers.splice( index, 1 );
+				}
+			}
+
+			// start the next in the queue if the last step wasn't forced
+			// timers currently will call their complete callbacks, which will dequeue
+			// but only if they were gotoEnd
+			if ( !( gotoEnd && hadTimers ) ) {
+				jQuery.dequeue( this, type );
+			}
+		});
+	}
+
+});
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+	setTimeout( clearFxNow, 0 );
+	return ( fxNow = jQuery.now() );
+}
+
+function clearFxNow() {
+	fxNow = undefined;
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, num ) {
+	var obj = {};
+
+	jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() {
+		obj[ this ] = type;
+	});
+
+	return obj;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+	slideDown: genFx( "show", 1 ),
+	slideUp: genFx( "hide", 1 ),
+	slideToggle: genFx( "toggle", 1 ),
+	fadeIn: { opacity: "show" },
+	fadeOut: { opacity: "hide" },
+	fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return this.animate( props, speed, easing, callback );
+	};
+});
+
+jQuery.extend({
+	speed: function( speed, easing, fn ) {
+		var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+			complete: fn || !fn && easing ||
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+		};
+
+		opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+			opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+		// normalize opt.queue - true/undefined/null -> "fx"
+		if ( opt.queue == null || opt.queue === true ) {
+			opt.queue = "fx";
+		}
+
+		// Queueing
+		opt.old = opt.complete;
+
+		opt.complete = function( noUnmark ) {
+			if ( jQuery.isFunction( opt.old ) ) {
+				opt.old.call( this );
+			}
+
+			if ( opt.queue ) {
+				jQuery.dequeue( this, opt.queue );
+			} else if ( noUnmark !== false ) {
+				jQuery._unmark( this );
+			}
+		};
+
+		return opt;
+	},
+
+	easing: {
+		linear: function( p, n, firstNum, diff ) {
+			return firstNum + diff * p;
+		},
+		swing: function( p, n, firstNum, diff ) {
+			return ( ( -Math.cos( p*Math.PI ) / 2 ) + 0.5 ) * diff + firstNum;
+		}
+	},
+
+	timers: [],
+
+	fx: function( elem, options, prop ) {
+		this.options = options;
+		this.elem = elem;
+		this.prop = prop;
+
+		options.orig = options.orig || {};
+	}
+
+});
+
+jQuery.fx.prototype = {
+	// Simple function for setting a style value
+	update: function() {
+		if ( this.options.step ) {
+			this.options.step.call( this.elem, this.now, this );
+		}
+
+		( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this );
+	},
+
+	// Get the current size
+	cur: function() {
+		if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) {
+			return this.elem[ this.prop ];
+		}
+
+		var parsed,
+			r = jQuery.css( this.elem, this.prop );
+		// Empty strings, null, undefined and "auto" are converted to 0,
+		// complex values such as "rotate(1rad)" are returned as is,
+		// simple values such as "10px" are parsed to Float.
+		return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
+	},
+
+	// Start an animation from one number to another
+	custom: function( from, to, unit ) {
+		var self = this,
+			fx = jQuery.fx;
+
+		this.startTime = fxNow || createFxNow();
+		this.end = to;
+		this.now = this.start = from;
+		this.pos = this.state = 0;
+		this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
+
+		function t( gotoEnd ) {
+			return self.step( gotoEnd );
+		}
+
+		t.queue = this.options.queue;
+		t.elem = this.elem;
+		t.saveState = function() {
+			if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
+				jQuery._data( self.elem, "fxshow" + self.prop, self.start );
+			}
+		};
+
+		if ( t() && jQuery.timers.push(t) && !timerId ) {
+			timerId = setInterval( fx.tick, fx.interval );
+		}
+	},
+
+	// Simple 'show' function
+	show: function() {
+		var dataShow = jQuery._data( this.elem, "fxshow" + this.prop );
+
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop );
+		this.options.show = true;
+
+		// Begin the animation
+		// Make sure that we start at a small width/height to avoid any flash of content
+		if ( dataShow !== undefined ) {
+			// This show is picking up where a previous hide or show left off
+			this.custom( this.cur(), dataShow );
+		} else {
+			this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() );
+		}
+
+		// Start by showing the element
+		jQuery( this.elem ).show();
+	},
+
+	// Simple 'hide' function
+	hide: function() {
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop );
+		this.options.hide = true;
+
+		// Begin the animation
+		this.custom( this.cur(), 0 );
+	},
+
+	// Each step of an animation
+	step: function( gotoEnd ) {
+		var p, n, complete,
+			t = fxNow || createFxNow(),
+			done = true,
+			elem = this.elem,
+			options = this.options;
+
+		if ( gotoEnd || t >= options.duration + this.startTime ) {
+			this.now = this.end;
+			this.pos = this.state = 1;
+			this.update();
+
+			options.animatedProperties[ this.prop ] = true;
+
+			for ( p in options.animatedProperties ) {
+				if ( options.animatedProperties[ p ] !== true ) {
+					done = false;
+				}
+			}
+
+			if ( done ) {
+				// Reset the overflow
+				if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+
+					jQuery.each( [ "", "X", "Y" ], function( index, value ) {
+						elem.style[ "overflow" + value ] = options.overflow[ index ];
+					});
+				}
+
+				// Hide the element if the "hide" operation was done
+				if ( options.hide ) {
+					jQuery( elem ).hide();
+				}
+
+				// Reset the properties, if the item has been hidden or shown
+				if ( options.hide || options.show ) {
+					for ( p in options.animatedProperties ) {
+						jQuery.style( elem, p, options.orig[ p ] );
+						jQuery.removeData( elem, "fxshow" + p, true );
+						// Toggle data is no longer needed
+						jQuery.removeData( elem, "toggle" + p, true );
+					}
+				}
+
+				// Execute the complete function
+				// in the event that the complete function throws an exception
+				// we must ensure it won't be called twice. #5684
+
+				complete = options.complete;
+				if ( complete ) {
+
+					options.complete = false;
+					complete.call( elem );
+				}
+			}
+
+			return false;
+
+		} else {
+			// classical easing cannot be used with an Infinity duration
+			if ( options.duration == Infinity ) {
+				this.now = t;
+			} else {
+				n = t - this.startTime;
+				this.state = n / options.duration;
+
+				// Perform the easing function, defaults to swing
+				this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration );
+				this.now = this.start + ( (this.end - this.start) * this.pos );
+			}
+			// Perform the next step of the animation
+			this.update();
+		}
+
+		return true;
+	}
+};
+
+jQuery.extend( jQuery.fx, {
+	tick: function() {
+		var timer,
+			timers = jQuery.timers,
+			i = 0;
+
+		for ( ; i < timers.length; i++ ) {
+			timer = timers[ i ];
+			// Checks the timer has not already been removed
+			if ( !timer() && timers[ i ] === timer ) {
+				timers.splice( i--, 1 );
+			}
+		}
+
+		if ( !timers.length ) {
+			jQuery.fx.stop();
+		}
+	},
+
+	interval: 13,
+
+	stop: function() {
+		clearInterval( timerId );
+		timerId = null;
+	},
+
+	speeds: {
+		slow: 600,
+		fast: 200,
+		// Default speed
+		_default: 400
+	},
+
+	step: {
+		opacity: function( fx ) {
+			jQuery.style( fx.elem, "opacity", fx.now );
+		},
+
+		_default: function( fx ) {
+			if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+				fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+			} else {
+				fx.elem[ fx.prop ] = fx.now;
+			}
+		}
+	}
+});
+
+// Adds width/height step functions
+// Do not set anything below 0
+jQuery.each([ "width", "height" ], function( i, prop ) {
+	jQuery.fx.step[ prop ] = function( fx ) {
+		jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit );
+	};
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.animated = function( elem ) {
+		return jQuery.grep(jQuery.timers, function( fn ) {
+			return elem === fn.elem;
+		}).length;
+	};
+}
+
+// Try to restore the default display value of an element
+function defaultDisplay( nodeName ) {
+
+	if ( !elemdisplay[ nodeName ] ) {
+
+		var body = document.body,
+			elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
+			display = elem.css( "display" );
+		elem.remove();
+
+		// If the simple way fails,
+		// get element's real default display by attaching it to a temp iframe
+		if ( display === "none" || display === "" ) {
+			// No iframe to use yet, so create it
+			if ( !iframe ) {
+				iframe = document.createElement( "iframe" );
+				iframe.frameBorder = iframe.width = iframe.height = 0;
+			}
+
+			body.appendChild( iframe );
+
+			// Create a cacheable copy of the iframe document on first call.
+			// IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+			// document to it; WebKit & Firefox won't allow reusing the iframe document.
+			if ( !iframeDoc || !iframe.createElement ) {
+				iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+				iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" );
+				iframeDoc.close();
+			}
+
+			elem = iframeDoc.createElement( nodeName );
+
+			iframeDoc.body.appendChild( elem );
+
+			display = jQuery.css( elem, "display" );
+			body.removeChild( iframe );
+		}
+
+		// Store the correct default display
+		elemdisplay[ nodeName ] = display;
+	}
+
+	return elemdisplay[ nodeName ];
+}
+
+
+
+
+var rtable = /^t(?:able|d|h)$/i,
+	rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+	jQuery.fn.offset = function( options ) {
+		var elem = this[0], box;
+
+		if ( options ) {
+			return this.each(function( i ) {
+				jQuery.offset.setOffset( this, options, i );
+			});
+		}
+
+		if ( !elem || !elem.ownerDocument ) {
+			return null;
+		}
+
+		if ( elem === elem.ownerDocument.body ) {
+			return jQuery.offset.bodyOffset( elem );
+		}
+
+		try {
+			box = elem.getBoundingClientRect();
+		} catch(e) {}
+
+		var doc = elem.ownerDocument,
+			docElem = doc.documentElement;
+
+		// Make sure we're not dealing with a disconnected DOM node
+		if ( !box || !jQuery.contains( docElem, elem ) ) {
+			return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+		}
+
+		var body = doc.body,
+			win = getWindow(doc),
+			clientTop  = docElem.clientTop  || body.clientTop  || 0,
+			clientLeft = docElem.clientLeft || body.clientLeft || 0,
+			scrollTop  = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop,
+			scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
+			top  = box.top  + scrollTop  - clientTop,
+			left = box.left + scrollLeft - clientLeft;
+
+		return { top: top, left: left };
+	};
+
+} else {
+	jQuery.fn.offset = function( options ) {
+		var elem = this[0];
+
+		if ( options ) {
+			return this.each(function( i ) {
+				jQuery.offset.setOffset( this, options, i );
+			});
+		}
+
+		if ( !elem || !elem.ownerDocument ) {
+			return null;
+		}
+
+		if ( elem === elem.ownerDocument.body ) {
+			return jQuery.offset.bodyOffset( elem );
+		}
+
+		var computedStyle,
+			offsetParent = elem.offsetParent,
+			prevOffsetParent = elem,
+			doc = elem.ownerDocument,
+			docElem = doc.documentElement,
+			body = doc.body,
+			defaultView = doc.defaultView,
+			prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+			top = elem.offsetTop,
+			left = elem.offsetLeft;
+
+		while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+			if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+				break;
+			}
+
+			computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+			top  -= elem.scrollTop;
+			left -= elem.scrollLeft;
+
+			if ( elem === offsetParent ) {
+				top  += elem.offsetTop;
+				left += elem.offsetLeft;
+
+				if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
+					top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+					left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+				}
+
+				prevOffsetParent = offsetParent;
+				offsetParent = elem.offsetParent;
+			}
+
+			if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+				top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+				left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+			}
+
+			prevComputedStyle = computedStyle;
+		}
+
+		if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+			top  += body.offsetTop;
+			left += body.offsetLeft;
+		}
+
+		if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+			top  += Math.max( docElem.scrollTop, body.scrollTop );
+			left += Math.max( docElem.scrollLeft, body.scrollLeft );
+		}
+
+		return { top: top, left: left };
+	};
+}
+
+jQuery.offset = {
+
+	bodyOffset: function( body ) {
+		var top = body.offsetTop,
+			left = body.offsetLeft;
+
+		if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
+			top  += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+			left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+		}
+
+		return { top: top, left: left };
+	},
+
+	setOffset: function( elem, options, i ) {
+		var position = jQuery.css( elem, "position" );
+
+		// set position first, in-case top/left are set even on static elem
+		if ( position === "static" ) {
+			elem.style.position = "relative";
+		}
+
+		var curElem = jQuery( elem ),
+			curOffset = curElem.offset(),
+			curCSSTop = jQuery.css( elem, "top" ),
+			curCSSLeft = jQuery.css( elem, "left" ),
+			calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+			props = {}, curPosition = {}, curTop, curLeft;
+
+		// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+		if ( calculatePosition ) {
+			curPosition = curElem.position();
+			curTop = curPosition.top;
+			curLeft = curPosition.left;
+		} else {
+			curTop = parseFloat( curCSSTop ) || 0;
+			curLeft = parseFloat( curCSSLeft ) || 0;
+		}
+
+		if ( jQuery.isFunction( options ) ) {
+			options = options.call( elem, i, curOffset );
+		}
+
+		if ( options.top != null ) {
+			props.top = ( options.top - curOffset.top ) + curTop;
+		}
+		if ( options.left != null ) {
+			props.left = ( options.left - curOffset.left ) + curLeft;
+		}
+
+		if ( "using" in options ) {
+			options.using.call( elem, props );
+		} else {
+			curElem.css( props );
+		}
+	}
+};
+
+
+jQuery.fn.extend({
+
+	position: function() {
+		if ( !this[0] ) {
+			return null;
+		}
+
+		var elem = this[0],
+
+		// Get *real* offsetParent
+		offsetParent = this.offsetParent(),
+
+		// Get correct offsets
+		offset       = this.offset(),
+		parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+		// Subtract element margins
+		// note: when an element has margin: auto the offsetLeft and marginLeft
+		// are the same in Safari causing offset.left to incorrectly be 0
+		offset.top  -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+		offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+		// Add offsetParent borders
+		parentOffset.top  += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+		parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+		// Subtract the two offsets
+		return {
+			top:  offset.top  - parentOffset.top,
+			left: offset.left - parentOffset.left
+		};
+	},
+
+	offsetParent: function() {
+		return this.map(function() {
+			var offsetParent = this.offsetParent || document.body;
+			while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+				offsetParent = offsetParent.offsetParent;
+			}
+			return offsetParent;
+		});
+	}
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ["Left", "Top"], function( i, name ) {
+	var method = "scroll" + name;
+
+	jQuery.fn[ method ] = function( val ) {
+		var elem, win;
+
+		if ( val === undefined ) {
+			elem = this[ 0 ];
+
+			if ( !elem ) {
+				return null;
+			}
+
+			win = getWindow( elem );
+
+			// Return the scroll offset
+			return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
+				jQuery.support.boxModel && win.document.documentElement[ method ] ||
+					win.document.body[ method ] :
+				elem[ method ];
+		}
+
+		// Set the scroll offset
+		return this.each(function() {
+			win = getWindow( this );
+
+			if ( win ) {
+				win.scrollTo(
+					!i ? val : jQuery( win ).scrollLeft(),
+					 i ? val : jQuery( win ).scrollTop()
+				);
+
+			} else {
+				this[ method ] = val;
+			}
+		});
+	};
+});
+
+function getWindow( elem ) {
+	return jQuery.isWindow( elem ) ?
+		elem :
+		elem.nodeType === 9 ?
+			elem.defaultView || elem.parentWindow :
+			false;
+}
+
+
+
+
+// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function( i, name ) {
+
+	var type = name.toLowerCase();
+
+	// innerHeight and innerWidth
+	jQuery.fn[ "inner" + name ] = function() {
+		var elem = this[0];
+		return elem ?
+			elem.style ?
+			parseFloat( jQuery.css( elem, type, "padding" ) ) :
+			this[ type ]() :
+			null;
+	};
+
+	// outerHeight and outerWidth
+	jQuery.fn[ "outer" + name ] = function( margin ) {
+		var elem = this[0];
+		return elem ?
+			elem.style ?
+			parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
+			this[ type ]() :
+			null;
+	};
+
+	jQuery.fn[ type ] = function( size ) {
+		// Get window width or height
+		var elem = this[0];
+		if ( !elem ) {
+			return size == null ? null : this;
+		}
+
+		if ( jQuery.isFunction( size ) ) {
+			return this.each(function( i ) {
+				var self = jQuery( this );
+				self[ type ]( size.call( this, i, self[ type ]() ) );
+			});
+		}
+
+		if ( jQuery.isWindow( elem ) ) {
+			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+			// 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+			var docElemProp = elem.document.documentElement[ "client" + name ],
+				body = elem.document.body;
+			return elem.document.compatMode === "CSS1Compat" && docElemProp ||
+				body && body[ "client" + name ] || docElemProp;
+
+		// Get document width or height
+		} else if ( elem.nodeType === 9 ) {
+			// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+			return Math.max(
+				elem.documentElement["client" + name],
+				elem.body["scroll" + name], elem.documentElement["scroll" + name],
+				elem.body["offset" + name], elem.documentElement["offset" + name]
+			);
+
+		// Get or set width or height on the element
+		} else if ( size === undefined ) {
+			var orig = jQuery.css( elem, type ),
+				ret = parseFloat( orig );
+
+			return jQuery.isNumeric( ret ) ? ret : orig;
+
+		// Set the width or height on the element (default to pixels if value is unitless)
+		} else {
+			return this.css( type, typeof size === "string" ? size : size + "px" );
+		}
+	};
+
+});
+
+
+
+
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+	define( "jquery", [], function () { return jQuery; } );
+}
+
+
+
+})( window );

+ 131 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/jquery/jquery.progressbar.js

@@ -0,0 +1,131 @@
+/*
+ * @ Dmitry Farafonov
+ */
+
+(function($){
+$.ProgressBar = function(options) {
+	this.element = $(options.boundingBox);
+	if (options.on && options.on.complete){
+		this.onComplete = options.on.complete;
+	}
+	if (options.on && options.on.valueChange){
+		this.onValueChange = options.on.valueChange;
+	}
+	
+	this._create();
+	
+	if (options.label)
+		this.set("label", options.label);
+	if (options.value)
+		this.value(options.value);
+	if (options.max)
+		this.set("max", options.max);
+};
+$.ProgressBar.prototype = {
+	options: {
+		value: 0,
+		max: 100
+	},
+
+	min: 0,
+
+	_create: function() {
+		this.element
+			.addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
+			.attr({
+				role: "progressbar",
+				"aria-valuemin": this.min,
+				"aria-valuemax": this.options.max,
+				"aria-valuenow": this._value()
+			});
+
+		this.valueDiv = $( "<div class='ui-progressbar-label'></div>" )
+			.appendTo( this.element );
+			
+		this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" )
+			.appendTo( this.element );
+
+		this.oldValue = this._value();
+		this._refreshValue();
+	},
+
+	_destroy: function() {
+		this.element
+			.removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
+			.removeAttr( "role" )
+			.removeAttr( "aria-valuemin" )
+			.removeAttr( "aria-valuemax" )
+			.removeAttr( "aria-valuenow" );
+
+		this.valueDiv.remove();
+	},
+
+	value: function( newValue ) {
+		if ( newValue === undefined ) {
+			return this._value();
+		}
+
+		this._setOption( "value", newValue );
+		return this;
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "value" ) {
+			//var oldVal = this.options.value;
+			this.options.value = value;
+			this._refreshValue();
+			
+			if (this.onValueChange)
+				this.onValueChange.apply(this, [{oldVal: this.oldValue, newVal: value}]);
+			
+			if ( this._value() === this.options.max ) {
+				//this._trigger( "complete" );
+				if (this.onComplete)
+					this.onComplete.apply(this);
+			}
+		} else if (key === "label") {
+			$(this.element).find(".ui-progressbar-label").html(value);
+		} else if (key === "max") {
+			this.options.max = value;
+		}
+
+		//this._super( key, value );
+	},
+
+	_value: function() {
+		var val = this.options.value;
+		// normalize invalid value
+		if ( typeof val !== "number" ) {
+			val = 0;
+		}
+		return Math.min( this.options.max, Math.max( this.min, val ) );
+	},
+
+	_percentage: function() {
+		return 100 * this._value() / this.options.max;
+	},
+
+	_refreshValue: function() {
+		var value = this.value(),
+			percentage = this._percentage();
+
+		if ( this.oldValue !== value ) {
+			this.oldValue = value;
+			//this._trigger( "change" );
+		}
+
+		this.valueDiv
+			.toggle( value > this.min )
+			.toggleClass( "ui-corner-right", value === this.options.max )
+			.width( percentage.toFixed(0) + "%" );
+		this.element.attr( "aria-valuenow", value );
+		
+		//$(this.element).find(".ui-progressbar-label").html(value + "%");
+	},
+	
+	set: function(key, value){
+		this._setOption(key, value);
+	}
+};
+
+})( jQuery );

+ 23 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/jstools.js

@@ -0,0 +1,23 @@
+if (typeof(console) == "undefined") {
+  var console = {
+    info: function(){},
+    warn: function(){},
+    error: function(){},
+    log: function(){},
+    time: function(){},
+    timeEnd: function(){}
+  };
+}
+
+if(!Array.isArray) {
+  Array.isArray = function (vArg) {
+    return Object.prototype.toString.call(vArg) === "[object Array]";
+  };
+}
+
+if (!Object.isSVGElement) {
+  Object.isSVGElement = function(vArg) {
+  var str = Object.prototype.toString.call(vArg);
+  return (str.indexOf("[object SVG") == 0);
+  };
+}

File diff suppressed because it is too large
+ 9 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/raphael.2.1.1.js


File diff suppressed because it is too large
+ 9 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/raphael.js


+ 5815 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/raphael_uncompressed.js

@@ -0,0 +1,5815 @@
+// ┌────────────────────────────────────────────────────────────────────┐ \\
+// │ Raphaël 2.1.0 - JavaScript Vector Library                          │ \\
+// ├────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com)    │ \\
+// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com)              │ \\
+// ├────────────────────────────────────────────────────────────────────┤ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
+// └────────────────────────────────────────────────────────────────────┘ \\
+
+// ┌──────────────────────────────────────────────────────────────────────────────────────┐ \\
+// │ Eve 0.3.4 - JavaScript Events Library                                                │ \\
+// ├──────────────────────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://dmitry.baranovskiy.com/)          │ \\
+// │ Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. │ \\
+// └──────────────────────────────────────────────────────────────────────────────────────┘ \\
+
+(function (glob) {
+    var version = "0.3.4",
+        has = "hasOwnProperty",
+        separator = /[\.\/]/,
+        wildcard = "*",
+        fun = function () {},
+        numsort = function (a, b) {
+            return a - b;
+        },
+        current_event,
+        stop,
+        events = {n: {}},
+    
+        eve = function (name, scope) {
+            var e = events,
+                oldstop = stop,
+                args = Array.prototype.slice.call(arguments, 2),
+                listeners = eve.listeners(name),
+                z = 0,
+                f = false,
+                l,
+                indexed = [],
+                queue = {},
+                out = [],
+                ce = current_event,
+                errors = [];
+            current_event = name;
+            stop = 0;
+            for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
+                indexed.push(listeners[i].zIndex);
+                if (listeners[i].zIndex < 0) {
+                    queue[listeners[i].zIndex] = listeners[i];
+                }
+            }
+            indexed.sort(numsort);
+            while (indexed[z] < 0) {
+                l = queue[indexed[z++]];
+                out.push(l.apply(scope, args));
+                if (stop) {
+                    stop = oldstop;
+                    return out;
+                }
+            }
+            for (i = 0; i < ii; i++) {
+                l = listeners[i];
+                if ("zIndex" in l) {
+                    if (l.zIndex == indexed[z]) {
+                        out.push(l.apply(scope, args));
+                        if (stop) {
+                            break;
+                        }
+                        do {
+                            z++;
+                            l = queue[indexed[z]];
+                            l && out.push(l.apply(scope, args));
+                            if (stop) {
+                                break;
+                            }
+                        } while (l)
+                    } else {
+                        queue[l.zIndex] = l;
+                    }
+                } else {
+                    out.push(l.apply(scope, args));
+                    if (stop) {
+                        break;
+                    }
+                }
+            }
+            stop = oldstop;
+            current_event = ce;
+            return out.length ? out : null;
+        };
+    
+    eve.listeners = function (name) {
+        var names = name.split(separator),
+            e = events,
+            item,
+            items,
+            k,
+            i,
+            ii,
+            j,
+            jj,
+            nes,
+            es = [e],
+            out = [];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            nes = [];
+            for (j = 0, jj = es.length; j < jj; j++) {
+                e = es[j].n;
+                items = [e[names[i]], e[wildcard]];
+                k = 2;
+                while (k--) {
+                    item = items[k];
+                    if (item) {
+                        nes.push(item);
+                        out = out.concat(item.f || []);
+                    }
+                }
+            }
+            es = nes;
+        }
+        return out;
+    };
+    
+    
+    eve.on = function (name, f) {
+        var names = name.split(separator),
+            e = events;
+        for (var i = 0, ii = names.length; i < ii; i++) {
+            e = e.n;
+            !e[names[i]] && (e[names[i]] = {n: {}});
+            e = e[names[i]];
+        }
+        e.f = e.f || [];
+        for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
+            return fun;
+        }
+        e.f.push(f);
+        return function (zIndex) {
+            if (+zIndex == +zIndex) {
+                f.zIndex = +zIndex;
+            }
+        };
+    };
+    
+    eve.stop = function () {
+        stop = 1;
+    };
+    
+    eve.nt = function (subname) {
+        if (subname) {
+            return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
+        }
+        return current_event;
+    };
+    
+    
+    eve.off = eve.unbind = function (name, f) {
+        var names = name.split(separator),
+            e,
+            key,
+            splice,
+            i, ii, j, jj,
+            cur = [events];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            for (j = 0; j < cur.length; j += splice.length - 2) {
+                splice = [j, 1];
+                e = cur[j].n;
+                if (names[i] != wildcard) {
+                    if (e[names[i]]) {
+                        splice.push(e[names[i]]);
+                    }
+                } else {
+                    for (key in e) if (e[has](key)) {
+                        splice.push(e[key]);
+                    }
+                }
+                cur.splice.apply(cur, splice);
+            }
+        }
+        for (i = 0, ii = cur.length; i < ii; i++) {
+            e = cur[i];
+            while (e.n) {
+                if (f) {
+                    if (e.f) {
+                        for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
+                            e.f.splice(j, 1);
+                            break;
+                        }
+                        !e.f.length && delete e.f;
+                    }
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        var funcs = e.n[key].f;
+                        for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
+                            funcs.splice(j, 1);
+                            break;
+                        }
+                        !funcs.length && delete e.n[key].f;
+                    }
+                } else {
+                    delete e.f;
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        delete e.n[key].f;
+                    }
+                }
+                e = e.n;
+            }
+        }
+    };
+    
+    eve.once = function (name, f) {
+        var f2 = function () {
+            var res = f.apply(this, arguments);
+            eve.unbind(name, f2);
+            return res;
+        };
+        return eve.on(name, f2);
+    };
+    
+    eve.version = version;
+    eve.toString = function () {
+        return "You are running Eve " + version;
+    };
+    (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define != "undefined" ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
+})(this);
+
+
+// ┌─────────────────────────────────────────────────────────────────────┐ \\
+// │ "Raphaël 2.1.0" - JavaScript Vector Library                         │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
+// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
+// └─────────────────────────────────────────────────────────────────────┘ \\
+(function () {
+    
+    function R(first) {
+        if (R.is(first, "function")) {
+            return loaded ? first() : eve.on("raphael.DOMload", first);
+        } else if (R.is(first, array)) {
+            return R._engine.create[apply](R, first.splice(0, 3 + R.is(first[0], nu))).add(first);
+        } else {
+            var args = Array.prototype.slice.call(arguments, 0);
+            if (R.is(args[args.length - 1], "function")) {
+                var f = args.pop();
+                return loaded ? f.call(R._engine.create[apply](R, args)) : eve.on("raphael.DOMload", function () {
+                    f.call(R._engine.create[apply](R, args));
+                });
+            } else {
+                return R._engine.create[apply](R, arguments);
+            }
+        }
+    }
+    R.version = "2.1.0";
+    R.eve = eve;
+    var loaded,
+        separator = /[, ]+/,
+        elements = {circle: 1, rect: 1, path: 1, ellipse: 1, text: 1, image: 1},
+        formatrg = /\{(\d+)\}/g,
+        proto = "prototype",
+        has = "hasOwnProperty",
+        g = {
+            doc: document,
+            win: window
+        },
+        oldRaphael = {
+            was: Object.prototype[has].call(g.win, "Raphael"),
+            is: g.win.Raphael
+        },
+        Paper = function () {
+            
+            
+            this.ca = this.customAttributes = {};
+        },
+        paperproto,
+        appendChild = "appendChild",
+        apply = "apply",
+        concat = "concat",
+        supportsTouch = "createTouch" in g.doc,
+        E = "",
+        S = " ",
+        Str = String,
+        split = "split",
+        events = "click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[split](S),
+        touchMap = {
+            mousedown: "touchstart",
+            mousemove: "touchmove",
+            mouseup: "touchend"
+        },
+        lowerCase = Str.prototype.toLowerCase,
+        math = Math,
+        mmax = math.max,
+        mmin = math.min,
+        abs = math.abs,
+        pow = math.pow,
+        PI = math.PI,
+        nu = "number",
+        string = "string",
+        array = "array",
+        toString = "toString",
+        fillString = "fill",
+        objectToString = Object.prototype.toString,
+        paper = {},
+        push = "push",
+        ISURL = R._ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
+        colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,
+        isnan = {"NaN": 1, "Infinity": 1, "-Infinity": 1},
+        bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+        round = math.round,
+        setAttribute = "setAttribute",
+        toFloat = parseFloat,
+        toInt = parseInt,
+        upperCase = Str.prototype.toUpperCase,
+        availableAttrs = R._availableAttrs = {
+            "arrow-end": "none",
+            "arrow-start": "none",
+            blur: 0,
+            "clip-rect": "0 0 1e9 1e9",
+            cursor: "default",
+            cx: 0,
+            cy: 0,
+            fill: "#fff",
+            "fill-opacity": 1,
+            font: '10px "Arial"',
+            "font-family": '"Arial"',
+            "font-size": "10",
+            "font-style": "normal",
+            "font-weight": 400,
+            gradient: 0,
+            height: 0,
+            href: "http://raphaeljs.com/",
+            "letter-spacing": 0,
+            opacity: 1,
+            path: "M0,0",
+            r: 0,
+            rx: 0,
+            ry: 0,
+            src: "",
+            stroke: "#000",
+            "stroke-dasharray": "",
+            "stroke-linecap": "butt",
+            "stroke-linejoin": "butt",
+            "stroke-miterlimit": 0,
+            "stroke-opacity": 1,
+            "stroke-width": 1,
+            target: "_blank",
+            "text-anchor": "middle",
+            title: "Raphael",
+            transform: "",
+            width: 0,
+            x: 0,
+            y: 0
+        },
+        availableAnimAttrs = R._availableAnimAttrs = {
+            blur: nu,
+            "clip-rect": "csv",
+            cx: nu,
+            cy: nu,
+            fill: "colour",
+            "fill-opacity": nu,
+            "font-size": nu,
+            height: nu,
+            opacity: nu,
+            path: "path",
+            r: nu,
+            rx: nu,
+            ry: nu,
+            stroke: "colour",
+            "stroke-opacity": nu,
+            "stroke-width": nu,
+            transform: "transform",
+            width: nu,
+            x: nu,
+            y: nu
+        },
+        whitespace = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]/g,
+        commaSpaces = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,
+        hsrg = {hs: 1, rg: 1},
+        p2s = /,?([achlmqrstvxz]),?/gi,
+        pathCommand = /([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
+        tCommand = /([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
+        pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig,
+        radial_gradient = R._radial_gradient = /^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,
+        eldata = {},
+        sortByKey = function (a, b) {
+            return a.key - b.key;
+        },
+        sortByNumber = function (a, b) {
+            return toFloat(a) - toFloat(b);
+        },
+        fun = function () {},
+        pipe = function (x) {
+            return x;
+        },
+        rectPath = R._rectPath = function (x, y, w, h, r) {
+            if (r) {
+                return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]];
+            }
+            return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
+        },
+        ellipsePath = function (x, y, rx, ry) {
+            if (ry == null) {
+                ry = rx;
+            }
+            return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]];
+        },
+        getPath = R._getPath = {
+            path: function (el) {
+                return el.attr("path");
+            },
+            circle: function (el) {
+                var a = el.attrs;
+                return ellipsePath(a.cx, a.cy, a.r);
+            },
+            ellipse: function (el) {
+                var a = el.attrs;
+                return ellipsePath(a.cx, a.cy, a.rx, a.ry);
+            },
+            rect: function (el) {
+                var a = el.attrs;
+                return rectPath(a.x, a.y, a.width, a.height, a.r);
+            },
+            image: function (el) {
+                var a = el.attrs;
+                return rectPath(a.x, a.y, a.width, a.height);
+            },
+            text: function (el) {
+                var bbox = el._getBBox();
+                return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
+            }
+        },
+        
+        mapPath = R.mapPath = function (path, matrix) {
+            if (!matrix) {
+                return path;
+            }
+            var x, y, i, j, ii, jj, pathi;
+            path = path2curve(path);
+            for (i = 0, ii = path.length; i < ii; i++) {
+                pathi = path[i];
+                for (j = 1, jj = pathi.length; j < jj; j += 2) {
+                    x = matrix.x(pathi[j], pathi[j + 1]);
+                    y = matrix.y(pathi[j], pathi[j + 1]);
+                    pathi[j] = x;
+                    pathi[j + 1] = y;
+                }
+            }
+            return path;
+        };
+
+    R._g = g;
+    
+    R.type = (g.win.SVGAngle || g.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML");
+    if (R.type == "VML") {
+        var d = g.doc.createElement("div"),
+            b;
+        d.innerHTML = '<v:shape adj="1"/>';
+        b = d.firstChild;
+        b.style.behavior = "url(#default#VML)";
+        if (!(b && typeof b.adj == "object")) {
+            return (R.type = E);
+        }
+        d = null;
+    }
+    
+    
+    R.svg = !(R.vml = R.type == "VML");
+    R._Paper = Paper;
+    
+    R.fn = paperproto = Paper.prototype = R.prototype;
+    R._id = 0;
+    R._oid = 0;
+    
+    R.is = function (o, type) {
+        type = lowerCase.call(type);
+        if (type == "finite") {
+            return !isnan[has](+o);
+        }
+        if (type == "array") {
+            return o instanceof Array;
+        }
+        return  (type == "null" && o === null) ||
+                (type == typeof o && o !== null) ||
+                (type == "object" && o === Object(o)) ||
+                (type == "array" && Array.isArray && Array.isArray(o)) ||
+                objectToString.call(o).slice(8, -1).toLowerCase() == type;
+    };
+
+    function clone(obj) {
+        if (Object(obj) !== obj) {
+            return obj;
+        }
+        var res = new obj.constructor;
+        for (var key in obj) if (obj[has](key)) {
+            res[key] = clone(obj[key]);
+        }
+        return res;
+    }
+
+    
+    R.angle = function (x1, y1, x2, y2, x3, y3) {
+        if (x3 == null) {
+            var x = x1 - x2,
+                y = y1 - y2;
+            if (!x && !y) {
+                return 0;
+            }
+            return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
+        } else {
+            return R.angle(x1, y1, x3, y3) - R.angle(x2, y2, x3, y3);
+        }
+    };
+    
+    R.rad = function (deg) {
+        return deg % 360 * PI / 180;
+    };
+    
+    R.deg = function (rad) {
+        return rad * 180 / PI % 360;
+    };
+    
+    R.snapTo = function (values, value, tolerance) {
+        tolerance = R.is(tolerance, "finite") ? tolerance : 10;
+        if (R.is(values, array)) {
+            var i = values.length;
+            while (i--) if (abs(values[i] - value) <= tolerance) {
+                return values[i];
+            }
+        } else {
+            values = +values;
+            var rem = value % values;
+            if (rem < tolerance) {
+                return value - rem;
+            }
+            if (rem > values - tolerance) {
+                return value - rem + values;
+            }
+        }
+        return value;
+    };
+    
+    
+    var createUUID = R.createUUID = (function (uuidRegEx, uuidReplacer) {
+        return function () {
+            return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(uuidRegEx, uuidReplacer).toUpperCase();
+        };
+    })(/[xy]/g, function (c) {
+        var r = math.random() * 16 | 0,
+            v = c == "x" ? r : (r & 3 | 8);
+        return v.toString(16);
+    });
+
+    
+    R.setWindow = function (newwin) {
+        eve("raphael.setWindow", R, g.win, newwin);
+        g.win = newwin;
+        g.doc = g.win.document;
+        if (R._engine.initWin) {
+            R._engine.initWin(g.win);
+        }
+    };
+    var toHex = function (color) {
+        if (R.vml) {
+            // http://dean.edwards.name/weblog/2009/10/convert-any-colour-value-to-hex-in-msie/
+            var trim = /^\s+|\s+$/g;
+            var bod;
+            try {
+                var docum = new ActiveXObject("htmlfile");
+                docum.write("<body>");
+                docum.close();
+                bod = docum.body;
+            } catch(e) {
+                bod = createPopup().document.body;
+            }
+            var range = bod.createTextRange();
+            toHex = cacher(function (color) {
+                try {
+                    bod.style.color = Str(color).replace(trim, E);
+                    var value = range.queryCommandValue("ForeColor");
+                    value = ((value & 255) << 16) | (value & 65280) | ((value & 16711680) >>> 16);
+                    return "#" + ("000000" + value.toString(16)).slice(-6);
+                } catch(e) {
+                    return "none";
+                }
+            });
+        } else {
+            var i = g.doc.createElement("i");
+            i.title = "Rapha\xebl Colour Picker";
+            i.style.display = "none";
+            g.doc.body.appendChild(i);
+            toHex = cacher(function (color) {
+                i.style.color = color;
+                return g.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
+            });
+        }
+        return toHex(color);
+    },
+    hsbtoString = function () {
+        return "hsb(" + [this.h, this.s, this.b] + ")";
+    },
+    hsltoString = function () {
+        return "hsl(" + [this.h, this.s, this.l] + ")";
+    },
+    rgbtoString = function () {
+        return this.hex;
+    },
+    prepareRGB = function (r, g, b) {
+        if (g == null && R.is(r, "object") && "r" in r && "g" in r && "b" in r) {
+            b = r.b;
+            g = r.g;
+            r = r.r;
+        }
+        if (g == null && R.is(r, string)) {
+            var clr = R.getRGB(r);
+            r = clr.r;
+            g = clr.g;
+            b = clr.b;
+        }
+        if (r > 1 || g > 1 || b > 1) {
+            r /= 255;
+            g /= 255;
+            b /= 255;
+        }
+        
+        return [r, g, b];
+    },
+    packageRGB = function (r, g, b, o) {
+        r *= 255;
+        g *= 255;
+        b *= 255;
+        var rgb = {
+            r: r,
+            g: g,
+            b: b,
+            hex: R.rgb(r, g, b),
+            toString: rgbtoString
+        };
+        R.is(o, "finite") && (rgb.opacity = o);
+        return rgb;
+    };
+    
+    
+    R.color = function (clr) {
+        var rgb;
+        if (R.is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
+            rgb = R.hsb2rgb(clr);
+            clr.r = rgb.r;
+            clr.g = rgb.g;
+            clr.b = rgb.b;
+            clr.hex = rgb.hex;
+        } else if (R.is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
+            rgb = R.hsl2rgb(clr);
+            clr.r = rgb.r;
+            clr.g = rgb.g;
+            clr.b = rgb.b;
+            clr.hex = rgb.hex;
+        } else {
+            if (R.is(clr, "string")) {
+                clr = R.getRGB(clr);
+            }
+            if (R.is(clr, "object") && "r" in clr && "g" in clr && "b" in clr) {
+                rgb = R.rgb2hsl(clr);
+                clr.h = rgb.h;
+                clr.s = rgb.s;
+                clr.l = rgb.l;
+                rgb = R.rgb2hsb(clr);
+                clr.v = rgb.b;
+            } else {
+                clr = {hex: "none"};
+                clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
+            }
+        }
+        clr.toString = rgbtoString;
+        return clr;
+    };
+    
+    R.hsb2rgb = function (h, s, v, o) {
+        if (this.is(h, "object") && "h" in h && "s" in h && "b" in h) {
+            v = h.b;
+            s = h.s;
+            h = h.h;
+            o = h.o;
+        }
+        h *= 360;
+        var R, G, B, X, C;
+        h = (h % 360) / 60;
+        C = v * s;
+        X = C * (1 - abs(h % 2 - 1));
+        R = G = B = v - C;
+
+        h = ~~h;
+        R += [C, X, 0, 0, X, C][h];
+        G += [X, C, C, X, 0, 0][h];
+        B += [0, 0, X, C, C, X][h];
+        return packageRGB(R, G, B, o);
+    };
+    
+    R.hsl2rgb = function (h, s, l, o) {
+        if (this.is(h, "object") && "h" in h && "s" in h && "l" in h) {
+            l = h.l;
+            s = h.s;
+            h = h.h;
+        }
+        if (h > 1 || s > 1 || l > 1) {
+            h /= 360;
+            s /= 100;
+            l /= 100;
+        }
+        h *= 360;
+        var R, G, B, X, C;
+        h = (h % 360) / 60;
+        C = 2 * s * (l < .5 ? l : 1 - l);
+        X = C * (1 - abs(h % 2 - 1));
+        R = G = B = l - C / 2;
+
+        h = ~~h;
+        R += [C, X, 0, 0, X, C][h];
+        G += [X, C, C, X, 0, 0][h];
+        B += [0, 0, X, C, C, X][h];
+        return packageRGB(R, G, B, o);
+    };
+    
+    R.rgb2hsb = function (r, g, b) {
+        b = prepareRGB(r, g, b);
+        r = b[0];
+        g = b[1];
+        b = b[2];
+
+        var H, S, V, C;
+        V = mmax(r, g, b);
+        C = V - mmin(r, g, b);
+        H = (C == 0 ? null :
+             V == r ? (g - b) / C :
+             V == g ? (b - r) / C + 2 :
+                      (r - g) / C + 4
+            );
+        H = ((H + 360) % 6) * 60 / 360;
+        S = C == 0 ? 0 : C / V;
+        return {h: H, s: S, b: V, toString: hsbtoString};
+    };
+    
+    R.rgb2hsl = function (r, g, b) {
+        b = prepareRGB(r, g, b);
+        r = b[0];
+        g = b[1];
+        b = b[2];
+
+        var H, S, L, M, m, C;
+        M = mmax(r, g, b);
+        m = mmin(r, g, b);
+        C = M - m;
+        H = (C == 0 ? null :
+             M == r ? (g - b) / C :
+             M == g ? (b - r) / C + 2 :
+                      (r - g) / C + 4);
+        H = ((H + 360) % 6) * 60 / 360;
+        L = (M + m) / 2;
+        S = (C == 0 ? 0 :
+             L < .5 ? C / (2 * L) :
+                      C / (2 - 2 * L));
+        return {h: H, s: S, l: L, toString: hsltoString};
+    };
+    R._path2string = function () {
+        return this.join(",").replace(p2s, "$1");
+    };
+    function repush(array, item) {
+        for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
+            return array.push(array.splice(i, 1)[0]);
+        }
+    }
+    function cacher(f, scope, postprocessor) {
+        function newf() {
+            var arg = Array.prototype.slice.call(arguments, 0),
+                args = arg.join("\u2400"),
+                cache = newf.cache = newf.cache || {},
+                count = newf.count = newf.count || [];
+            if (cache[has](args)) {
+                repush(count, args);
+                return postprocessor ? postprocessor(cache[args]) : cache[args];
+            }
+            count.length >= 1e3 && delete cache[count.shift()];
+            count.push(args);
+            cache[args] = f[apply](scope, arg);
+            return postprocessor ? postprocessor(cache[args]) : cache[args];
+        }
+        return newf;
+    }
+
+    var preload = R._preload = function (src, f) {
+        var img = g.doc.createElement("img");
+        img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
+        img.onload = function () {
+            f.call(this);
+            this.onload = null;
+            g.doc.body.removeChild(this);
+        };
+        img.onerror = function () {
+            g.doc.body.removeChild(this);
+        };
+        g.doc.body.appendChild(img);
+        img.src = src;
+    };
+    
+    function clrToString() {
+        return this.hex;
+    }
+
+    
+    R.getRGB = cacher(function (colour) {
+        if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
+            return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
+        }
+        if (colour == "none") {
+            return {r: -1, g: -1, b: -1, hex: "none", toString: clrToString};
+        }
+        !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
+        var res,
+            red,
+            green,
+            blue,
+            opacity,
+            t,
+            values,
+            rgb = colour.match(colourRegExp);
+        if (rgb) {
+            if (rgb[2]) {
+                blue = toInt(rgb[2].substring(5), 16);
+                green = toInt(rgb[2].substring(3, 5), 16);
+                red = toInt(rgb[2].substring(1, 3), 16);
+            }
+            if (rgb[3]) {
+                blue = toInt((t = rgb[3].charAt(3)) + t, 16);
+                green = toInt((t = rgb[3].charAt(2)) + t, 16);
+                red = toInt((t = rgb[3].charAt(1)) + t, 16);
+            }
+            if (rgb[4]) {
+                values = rgb[4][split](commaSpaces);
+                red = toFloat(values[0]);
+                values[0].slice(-1) == "%" && (red *= 2.55);
+                green = toFloat(values[1]);
+                values[1].slice(-1) == "%" && (green *= 2.55);
+                blue = toFloat(values[2]);
+                values[2].slice(-1) == "%" && (blue *= 2.55);
+                rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
+                values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+            }
+            if (rgb[5]) {
+                values = rgb[5][split](commaSpaces);
+                red = toFloat(values[0]);
+                values[0].slice(-1) == "%" && (red *= 2.55);
+                green = toFloat(values[1]);
+                values[1].slice(-1) == "%" && (green *= 2.55);
+                blue = toFloat(values[2]);
+                values[2].slice(-1) == "%" && (blue *= 2.55);
+                (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+                rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
+                values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+                return R.hsb2rgb(red, green, blue, opacity);
+            }
+            if (rgb[6]) {
+                values = rgb[6][split](commaSpaces);
+                red = toFloat(values[0]);
+                values[0].slice(-1) == "%" && (red *= 2.55);
+                green = toFloat(values[1]);
+                values[1].slice(-1) == "%" && (green *= 2.55);
+                blue = toFloat(values[2]);
+                values[2].slice(-1) == "%" && (blue *= 2.55);
+                (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+                rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
+                values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+                return R.hsl2rgb(red, green, blue, opacity);
+            }
+            rgb = {r: red, g: green, b: blue, toString: clrToString};
+            rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
+            R.is(opacity, "finite") && (rgb.opacity = opacity);
+            return rgb;
+        }
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
+    }, R);
+    
+    R.hsb = cacher(function (h, s, b) {
+        return R.hsb2rgb(h, s, b).hex;
+    });
+    
+    R.hsl = cacher(function (h, s, l) {
+        return R.hsl2rgb(h, s, l).hex;
+    });
+    
+    R.rgb = cacher(function (r, g, b) {
+        return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
+    });
+    
+    R.getColor = function (value) {
+        var start = this.getColor.start = this.getColor.start || {h: 0, s: 1, b: value || .75},
+            rgb = this.hsb2rgb(start.h, start.s, start.b);
+        start.h += .075;
+        if (start.h > 1) {
+            start.h = 0;
+            start.s -= .2;
+            start.s <= 0 && (this.getColor.start = {h: 0, s: 1, b: start.b});
+        }
+        return rgb.hex;
+    };
+    
+    R.getColor.reset = function () {
+        delete this.start;
+    };
+
+    // http://schepers.cc/getting-to-the-point
+    function catmullRom2bezier(crp, z) {
+        var d = [];
+        for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
+            var p = [
+                        {x: +crp[i - 2], y: +crp[i - 1]},
+                        {x: +crp[i],     y: +crp[i + 1]},
+                        {x: +crp[i + 2], y: +crp[i + 3]},
+                        {x: +crp[i + 4], y: +crp[i + 5]}
+                    ];
+            if (z) {
+                if (!i) {
+                    p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
+                } else if (iLen - 4 == i) {
+                    p[3] = {x: +crp[0], y: +crp[1]};
+                } else if (iLen - 2 == i) {
+                    p[2] = {x: +crp[0], y: +crp[1]};
+                    p[3] = {x: +crp[2], y: +crp[3]};
+                }
+            } else {
+                if (iLen - 4 == i) {
+                    p[3] = p[2];
+                } else if (!i) {
+                    p[0] = {x: +crp[i], y: +crp[i + 1]};
+                }
+            }
+            d.push(["C",
+                  (-p[0].x + 6 * p[1].x + p[2].x) / 6,
+                  (-p[0].y + 6 * p[1].y + p[2].y) / 6,
+                  (p[1].x + 6 * p[2].x - p[3].x) / 6,
+                  (p[1].y + 6*p[2].y - p[3].y) / 6,
+                  p[2].x,
+                  p[2].y
+            ]);
+        }
+
+        return d;
+    }
+    
+    R.parsePathString = function (pathString) {
+        if (!pathString) {
+            return null;
+        }
+        var pth = paths(pathString);
+        if (pth.arr) {
+            return pathClone(pth.arr);
+        }
+        
+        var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0},
+            data = [];
+        if (R.is(pathString, array) && R.is(pathString[0], array)) { // rough assumption
+            data = pathClone(pathString);
+        }
+        if (!data.length) {
+            Str(pathString).replace(pathCommand, function (a, b, c) {
+                var params = [],
+                    name = b.toLowerCase();
+                c.replace(pathValues, function (a, b) {
+                    b && params.push(+b);
+                });
+                if (name == "m" && params.length > 2) {
+                    data.push([b][concat](params.splice(0, 2)));
+                    name = "l";
+                    b = b == "m" ? "l" : "L";
+                }
+                if (name == "r") {
+                    data.push([b][concat](params));
+                } else while (params.length >= paramCounts[name]) {
+                    data.push([b][concat](params.splice(0, paramCounts[name])));
+                    if (!paramCounts[name]) {
+                        break;
+                    }
+                }
+            });
+        }
+        data.toString = R._path2string;
+        pth.arr = pathClone(data);
+        return data;
+    };
+    
+    R.parseTransformString = cacher(function (TString) {
+        if (!TString) {
+            return null;
+        }
+        var paramCounts = {r: 3, s: 4, t: 2, m: 6},
+            data = [];
+        if (R.is(TString, array) && R.is(TString[0], array)) { // rough assumption
+            data = pathClone(TString);
+        }
+        if (!data.length) {
+            Str(TString).replace(tCommand, function (a, b, c) {
+                var params = [],
+                    name = lowerCase.call(b);
+                c.replace(pathValues, function (a, b) {
+                    b && params.push(+b);
+                });
+                data.push([b][concat](params));
+            });
+        }
+        data.toString = R._path2string;
+        return data;
+    });
+    // PATHS
+    var paths = function (ps) {
+        var p = paths.ps = paths.ps || {};
+        if (p[ps]) {
+            p[ps].sleep = 100;
+        } else {
+            p[ps] = {
+                sleep: 100
+            };
+        }
+        setTimeout(function () {
+            for (var key in p) if (p[has](key) && key != ps) {
+                p[key].sleep--;
+                !p[key].sleep && delete p[key];
+            }
+        });
+        return p[ps];
+    };
+    
+    R.findDotsAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t,
+            t13 = pow(t1, 3),
+            t12 = pow(t1, 2),
+            t2 = t * t,
+            t3 = t2 * t,
+            x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
+            y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
+            mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
+            my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
+            nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
+            ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
+            ax = t1 * p1x + t * c1x,
+            ay = t1 * p1y + t * c1y,
+            cx = t1 * c2x + t * p2x,
+            cy = t1 * c2y + t * p2y,
+            alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
+        (mx > nx || my < ny) && (alpha += 180);
+        return {
+            x: x,
+            y: y,
+            m: {x: mx, y: my},
+            n: {x: nx, y: ny},
+            start: {x: ax, y: ay},
+            end: {x: cx, y: cy},
+            alpha: alpha
+        };
+    };
+    
+    R.bezierBBox = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+        if (!R.is(p1x, "array")) {
+            p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
+        }
+        var bbox = curveDim.apply(null, p1x);
+        return {
+            x: bbox.min.x,
+            y: bbox.min.y,
+            x2: bbox.max.x,
+            y2: bbox.max.y,
+            width: bbox.max.x - bbox.min.x,
+            height: bbox.max.y - bbox.min.y
+        };
+    };
+    
+    R.isPointInsideBBox = function (bbox, x, y) {
+        return x >= bbox.x && x <= bbox.x2 && y >= bbox.y && y <= bbox.y2;
+    };
+    
+    R.isBBoxIntersect = function (bbox1, bbox2) {
+        var i = R.isPointInsideBBox;
+        return i(bbox2, bbox1.x, bbox1.y)
+            || i(bbox2, bbox1.x2, bbox1.y)
+            || i(bbox2, bbox1.x, bbox1.y2)
+            || i(bbox2, bbox1.x2, bbox1.y2)
+            || i(bbox1, bbox2.x, bbox2.y)
+            || i(bbox1, bbox2.x2, bbox2.y)
+            || i(bbox1, bbox2.x, bbox2.y2)
+            || i(bbox1, bbox2.x2, bbox2.y2)
+            || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
+            && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
+    };
+    function base3(t, p1, p2, p3, p4) {
+        var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
+            t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
+        return t * t2 - 3 * p1 + 3 * p2;
+    }
+    function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
+        if (z == null) {
+            z = 1;
+        }
+        z = z > 1 ? 1 : z < 0 ? 0 : z;
+        var z2 = z / 2,
+            n = 12,
+            Tvalues = [-0.1252,0.1252,-0.3678,0.3678,-0.5873,0.5873,-0.7699,0.7699,-0.9041,0.9041,-0.9816,0.9816],
+            Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
+            sum = 0;
+        for (var i = 0; i < n; i++) {
+            var ct = z2 * Tvalues[i] + z2,
+                xbase = base3(ct, x1, x2, x3, x4),
+                ybase = base3(ct, y1, y2, y3, y4),
+                comb = xbase * xbase + ybase * ybase;
+            sum += Cvalues[i] * math.sqrt(comb);
+        }
+        return z2 * sum;
+    }
+    function getTatLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
+        if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
+            return;
+        }
+        var t = 1,
+            step = t / 2,
+            t2 = t - step,
+            l,
+            e = .01;
+        l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        while (abs(l - ll) > e) {
+            step /= 2;
+            t2 += (l < ll ? 1 : -1) * step;
+            l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        }
+        return t2;
+    }
+    function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
+        if (
+            mmax(x1, x2) < mmin(x3, x4) ||
+            mmin(x1, x2) > mmax(x3, x4) ||
+            mmax(y1, y2) < mmin(y3, y4) ||
+            mmin(y1, y2) > mmax(y3, y4)
+        ) {
+            return;
+        }
+        var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
+            ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
+            denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+
+        if (!denominator) {
+            return;
+        }
+        var px = nx / denominator,
+            py = ny / denominator,
+            px2 = +px.toFixed(2),
+            py2 = +py.toFixed(2);
+        if (
+            px2 < +mmin(x1, x2).toFixed(2) ||
+            px2 > +mmax(x1, x2).toFixed(2) ||
+            px2 < +mmin(x3, x4).toFixed(2) ||
+            px2 > +mmax(x3, x4).toFixed(2) ||
+            py2 < +mmin(y1, y2).toFixed(2) ||
+            py2 > +mmax(y1, y2).toFixed(2) ||
+            py2 < +mmin(y3, y4).toFixed(2) ||
+            py2 > +mmax(y3, y4).toFixed(2)
+        ) {
+            return;
+        }
+        return {x: px, y: py};
+    }
+    function inter(bez1, bez2) {
+        return interHelper(bez1, bez2);
+    }
+    function interCount(bez1, bez2) {
+        return interHelper(bez1, bez2, 1);
+    }
+    function interHelper(bez1, bez2, justCount) {
+        var bbox1 = R.bezierBBox(bez1),
+            bbox2 = R.bezierBBox(bez2);
+        if (!R.isBBoxIntersect(bbox1, bbox2)) {
+            return justCount ? 0 : [];
+        }
+        var l1 = bezlen.apply(0, bez1),
+            l2 = bezlen.apply(0, bez2),
+            n1 = ~~(l1 / 5),
+            n2 = ~~(l2 / 5),
+            dots1 = [],
+            dots2 = [],
+            xy = {},
+            res = justCount ? 0 : [];
+        for (var i = 0; i < n1 + 1; i++) {
+            var p = R.findDotsAtSegment.apply(R, bez1.concat(i / n1));
+            dots1.push({x: p.x, y: p.y, t: i / n1});
+        }
+        for (i = 0; i < n2 + 1; i++) {
+            p = R.findDotsAtSegment.apply(R, bez2.concat(i / n2));
+            dots2.push({x: p.x, y: p.y, t: i / n2});
+        }
+        for (i = 0; i < n1; i++) {
+            for (var j = 0; j < n2; j++) {
+                var di = dots1[i],
+                    di1 = dots1[i + 1],
+                    dj = dots2[j],
+                    dj1 = dots2[j + 1],
+                    ci = abs(di1.x - di.x) < .001 ? "y" : "x",
+                    cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
+                    is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
+                if (is) {
+                    if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
+                        continue;
+                    }
+                    xy[is.x.toFixed(4)] = is.y.toFixed(4);
+                    var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
+                        t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
+                    if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
+                        if (justCount) {
+                            res++;
+                        } else {
+                            res.push({
+                                x: is.x,
+                                y: is.y,
+                                t1: t1,
+                                t2: t2
+                            });
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    
+    R.pathIntersection = function (path1, path2) {
+        return interPathHelper(path1, path2);
+    };
+    R.pathIntersectionNumber = function (path1, path2) {
+        return interPathHelper(path1, path2, 1);
+    };
+    function interPathHelper(path1, path2, justCount) {
+        path1 = R._path2curve(path1);
+        path2 = R._path2curve(path2);
+        var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
+            res = justCount ? 0 : [];
+        for (var i = 0, ii = path1.length; i < ii; i++) {
+            var pi = path1[i];
+            if (pi[0] == "M") {
+                x1 = x1m = pi[1];
+                y1 = y1m = pi[2];
+            } else {
+                if (pi[0] == "C") {
+                    bez1 = [x1, y1].concat(pi.slice(1));
+                    x1 = bez1[6];
+                    y1 = bez1[7];
+                } else {
+                    bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
+                    x1 = x1m;
+                    y1 = y1m;
+                }
+                for (var j = 0, jj = path2.length; j < jj; j++) {
+                    var pj = path2[j];
+                    if (pj[0] == "M") {
+                        x2 = x2m = pj[1];
+                        y2 = y2m = pj[2];
+                    } else {
+                        if (pj[0] == "C") {
+                            bez2 = [x2, y2].concat(pj.slice(1));
+                            x2 = bez2[6];
+                            y2 = bez2[7];
+                        } else {
+                            bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
+                            x2 = x2m;
+                            y2 = y2m;
+                        }
+                        var intr = interHelper(bez1, bez2, justCount);
+                        if (justCount) {
+                            res += intr;
+                        } else {
+                            for (var k = 0, kk = intr.length; k < kk; k++) {
+                                intr[k].segment1 = i;
+                                intr[k].segment2 = j;
+                                intr[k].bez1 = bez1;
+                                intr[k].bez2 = bez2;
+                            }
+                            res = res.concat(intr);
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    
+    R.isPointInsidePath = function (path, x, y) {
+        var bbox = R.pathBBox(path);
+        return R.isPointInsideBBox(bbox, x, y) &&
+               interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
+    };
+    R._removedFactory = function (methodname) {
+        return function () {
+            eve("raphael.log", null, "Rapha\xebl: you are calling to method \u201c" + methodname + "\u201d of removed object", methodname);
+        };
+    };
+    
+    var pathDimensions = R.pathBBox = function (path) {
+        var pth = paths(path);
+        if (pth.bbox) {
+            return pth.bbox;
+        }
+        if (!path) {
+            return {x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0};
+        }
+        path = path2curve(path);
+        var x = 0, 
+            y = 0,
+            X = [],
+            Y = [],
+            p;
+        for (var i = 0, ii = path.length; i < ii; i++) {
+            p = path[i];
+            if (p[0] == "M") {
+                x = p[1];
+                y = p[2];
+                X.push(x);
+                Y.push(y);
+            } else {
+                var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                X = X[concat](dim.min.x, dim.max.x);
+                Y = Y[concat](dim.min.y, dim.max.y);
+                x = p[5];
+                y = p[6];
+            }
+        }
+        var xmin = mmin[apply](0, X),
+            ymin = mmin[apply](0, Y),
+            xmax = mmax[apply](0, X),
+            ymax = mmax[apply](0, Y),
+            bb = {
+                x: xmin,
+                y: ymin,
+                x2: xmax,
+                y2: ymax,
+                width: xmax - xmin,
+                height: ymax - ymin
+            };
+        pth.bbox = clone(bb);
+        return bb;
+    },
+        pathClone = function (pathArray) {
+            var res = clone(pathArray);
+            res.toString = R._path2string;
+            return res;
+        },
+        pathToRelative = R._pathToRelative = function (pathArray) {
+            var pth = paths(pathArray);
+            if (pth.rel) {
+                return pathClone(pth.rel);
+            }
+            if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
+                pathArray = R.parsePathString(pathArray);
+            }
+            var res = [],
+                x = 0,
+                y = 0,
+                mx = 0,
+                my = 0,
+                start = 0;
+            if (pathArray[0][0] == "M") {
+                x = pathArray[0][1];
+                y = pathArray[0][2];
+                mx = x;
+                my = y;
+                start++;
+                res.push(["M", x, y]);
+            }
+            for (var i = start, ii = pathArray.length; i < ii; i++) {
+                var r = res[i] = [],
+                    pa = pathArray[i];
+                if (pa[0] != lowerCase.call(pa[0])) {
+                    r[0] = lowerCase.call(pa[0]);
+                    switch (r[0]) {
+                        case "a":
+                            r[1] = pa[1];
+                            r[2] = pa[2];
+                            r[3] = pa[3];
+                            r[4] = pa[4];
+                            r[5] = pa[5];
+                            r[6] = +(pa[6] - x).toFixed(3);
+                            r[7] = +(pa[7] - y).toFixed(3);
+                            break;
+                        case "v":
+                            r[1] = +(pa[1] - y).toFixed(3);
+                            break;
+                        case "m":
+                            mx = pa[1];
+                            my = pa[2];
+                        default:
+                            for (var j = 1, jj = pa.length; j < jj; j++) {
+                                r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
+                            }
+                    }
+                } else {
+                    r = res[i] = [];
+                    if (pa[0] == "m") {
+                        mx = pa[1] + x;
+                        my = pa[2] + y;
+                    }
+                    for (var k = 0, kk = pa.length; k < kk; k++) {
+                        res[i][k] = pa[k];
+                    }
+                }
+                var len = res[i].length;
+                switch (res[i][0]) {
+                    case "z":
+                        x = mx;
+                        y = my;
+                        break;
+                    case "h":
+                        x += +res[i][len - 1];
+                        break;
+                    case "v":
+                        y += +res[i][len - 1];
+                        break;
+                    default:
+                        x += +res[i][len - 2];
+                        y += +res[i][len - 1];
+                }
+            }
+            res.toString = R._path2string;
+            pth.rel = pathClone(res);
+            return res;
+        },
+        pathToAbsolute = R._pathToAbsolute = function (pathArray) {
+            var pth = paths(pathArray);
+            if (pth.abs) {
+                return pathClone(pth.abs);
+            }
+            if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
+                pathArray = R.parsePathString(pathArray);
+            }
+            if (!pathArray || !pathArray.length) {
+                return [["M", 0, 0]];
+            }
+            var res = [],
+                x = 0,
+                y = 0,
+                mx = 0,
+                my = 0,
+                start = 0;
+            if (pathArray[0][0] == "M") {
+                x = +pathArray[0][1];
+                y = +pathArray[0][2];
+                mx = x;
+                my = y;
+                start++;
+                res[0] = ["M", x, y];
+            }
+            var crz = pathArray.length == 3 && pathArray[0][0] == "M" && pathArray[1][0].toUpperCase() == "R" && pathArray[2][0].toUpperCase() == "Z";
+            for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
+                res.push(r = []);
+                pa = pathArray[i];
+                if (pa[0] != upperCase.call(pa[0])) {
+                    r[0] = upperCase.call(pa[0]);
+                    switch (r[0]) {
+                        case "A":
+                            r[1] = pa[1];
+                            r[2] = pa[2];
+                            r[3] = pa[3];
+                            r[4] = pa[4];
+                            r[5] = pa[5];
+                            r[6] = +(pa[6] + x);
+                            r[7] = +(pa[7] + y);
+                            break;
+                        case "V":
+                            r[1] = +pa[1] + y;
+                            break;
+                        case "H":
+                            r[1] = +pa[1] + x;
+                            break;
+                        case "R":
+                            var dots = [x, y][concat](pa.slice(1));
+                            for (var j = 2, jj = dots.length; j < jj; j++) {
+                                dots[j] = +dots[j] + x;
+                                dots[++j] = +dots[j] + y;
+                            }
+                            res.pop();
+                            res = res[concat](catmullRom2bezier(dots, crz));
+                            break;
+                        case "M":
+                            mx = +pa[1] + x;
+                            my = +pa[2] + y;
+                        default:
+                            for (j = 1, jj = pa.length; j < jj; j++) {
+                                r[j] = +pa[j] + ((j % 2) ? x : y);
+                            }
+                    }
+                } else if (pa[0] == "R") {
+                    dots = [x, y][concat](pa.slice(1));
+                    res.pop();
+                    res = res[concat](catmullRom2bezier(dots, crz));
+                    r = ["R"][concat](pa.slice(-2));
+                } else {
+                    for (var k = 0, kk = pa.length; k < kk; k++) {
+                        r[k] = pa[k];
+                    }
+                }
+                switch (r[0]) {
+                    case "Z":
+                        x = mx;
+                        y = my;
+                        break;
+                    case "H":
+                        x = r[1];
+                        break;
+                    case "V":
+                        y = r[1];
+                        break;
+                    case "M":
+                        mx = r[r.length - 2];
+                        my = r[r.length - 1];
+                    default:
+                        x = r[r.length - 2];
+                        y = r[r.length - 1];
+                }
+            }
+            res.toString = R._path2string;
+            pth.abs = pathClone(res);
+            return res;
+        },
+        l2c = function (x1, y1, x2, y2) {
+            return [x1, y1, x2, y2, x2, y2];
+        },
+        q2c = function (x1, y1, ax, ay, x2, y2) {
+            var _13 = 1 / 3,
+                _23 = 2 / 3;
+            return [
+                    _13 * x1 + _23 * ax,
+                    _13 * y1 + _23 * ay,
+                    _13 * x2 + _23 * ax,
+                    _13 * y2 + _23 * ay,
+                    x2,
+                    y2
+                ];
+        },
+        a2c = function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+            // for more information of where this math came from visit:
+            // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+            var _120 = PI * 120 / 180,
+                rad = PI / 180 * (+angle || 0),
+                res = [],
+                xy,
+                rotate = cacher(function (x, y, rad) {
+                    var X = x * math.cos(rad) - y * math.sin(rad),
+                        Y = x * math.sin(rad) + y * math.cos(rad);
+                    return {x: X, y: Y};
+                });
+            if (!recursive) {
+                xy = rotate(x1, y1, -rad);
+                x1 = xy.x;
+                y1 = xy.y;
+                xy = rotate(x2, y2, -rad);
+                x2 = xy.x;
+                y2 = xy.y;
+                var cos = math.cos(PI / 180 * angle),
+                    sin = math.sin(PI / 180 * angle),
+                    x = (x1 - x2) / 2,
+                    y = (y1 - y2) / 2;
+                var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+                if (h > 1) {
+                    h = math.sqrt(h);
+                    rx = h * rx;
+                    ry = h * ry;
+                }
+                var rx2 = rx * rx,
+                    ry2 = ry * ry,
+                    k = (large_arc_flag == sweep_flag ? -1 : 1) *
+                        math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
+                    cx = k * rx * y / ry + (x1 + x2) / 2,
+                    cy = k * -ry * x / rx + (y1 + y2) / 2,
+                    f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
+                    f2 = math.asin(((y2 - cy) / ry).toFixed(9));
+
+                f1 = x1 < cx ? PI - f1 : f1;
+                f2 = x2 < cx ? PI - f2 : f2;
+                f1 < 0 && (f1 = PI * 2 + f1);
+                f2 < 0 && (f2 = PI * 2 + f2);
+                if (sweep_flag && f1 > f2) {
+                    f1 = f1 - PI * 2;
+                }
+                if (!sweep_flag && f2 > f1) {
+                    f2 = f2 - PI * 2;
+                }
+            } else {
+                f1 = recursive[0];
+                f2 = recursive[1];
+                cx = recursive[2];
+                cy = recursive[3];
+            }
+            var df = f2 - f1;
+            if (abs(df) > _120) {
+                var f2old = f2,
+                    x2old = x2,
+                    y2old = y2;
+                f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+                x2 = cx + rx * math.cos(f2);
+                y2 = cy + ry * math.sin(f2);
+                res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+            }
+            df = f2 - f1;
+            var c1 = math.cos(f1),
+                s1 = math.sin(f1),
+                c2 = math.cos(f2),
+                s2 = math.sin(f2),
+                t = math.tan(df / 4),
+                hx = 4 / 3 * rx * t,
+                hy = 4 / 3 * ry * t,
+                m1 = [x1, y1],
+                m2 = [x1 + hx * s1, y1 - hy * c1],
+                m3 = [x2 + hx * s2, y2 - hy * c2],
+                m4 = [x2, y2];
+            m2[0] = 2 * m1[0] - m2[0];
+            m2[1] = 2 * m1[1] - m2[1];
+            if (recursive) {
+                return [m2, m3, m4][concat](res);
+            } else {
+                res = [m2, m3, m4][concat](res).join()[split](",");
+                var newres = [];
+                for (var i = 0, ii = res.length; i < ii; i++) {
+                    newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
+                }
+                return newres;
+            }
+        },
+        findDotAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+            var t1 = 1 - t;
+            return {
+                x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
+                y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y
+            };
+        },
+        curveDim = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+            var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
+                b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
+                c = p1x - c1x,
+                t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a,
+                t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a,
+                y = [p1y, p2y],
+                x = [p1x, p2x],
+                dot;
+            abs(t1) > "1e12" && (t1 = .5);
+            abs(t2) > "1e12" && (t2 = .5);
+            if (t1 > 0 && t1 < 1) {
+                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
+                x.push(dot.x);
+                y.push(dot.y);
+            }
+            if (t2 > 0 && t2 < 1) {
+                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
+                x.push(dot.x);
+                y.push(dot.y);
+            }
+            a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
+            b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
+            c = p1y - c1y;
+            t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a;
+            t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a;
+            abs(t1) > "1e12" && (t1 = .5);
+            abs(t2) > "1e12" && (t2 = .5);
+            if (t1 > 0 && t1 < 1) {
+                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
+                x.push(dot.x);
+                y.push(dot.y);
+            }
+            if (t2 > 0 && t2 < 1) {
+                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
+                x.push(dot.x);
+                y.push(dot.y);
+            }
+            return {
+                min: {x: mmin[apply](0, x), y: mmin[apply](0, y)},
+                max: {x: mmax[apply](0, x), y: mmax[apply](0, y)}
+            };
+        }),
+        path2curve = R._path2curve = cacher(function (path, path2) {
+            var pth = !path2 && paths(path);
+            if (!path2 && pth.curve) {
+                return pathClone(pth.curve);
+            }
+            var p = pathToAbsolute(path),
+                p2 = path2 && pathToAbsolute(path2),
+                attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+                attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+                processPath = function (path, d) {
+                    var nx, ny;
+                    if (!path) {
+                        return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
+                    }
+                    !(path[0] in {T:1, Q:1}) && (d.qx = d.qy = null);
+                    switch (path[0]) {
+                        case "M":
+                            d.X = path[1];
+                            d.Y = path[2];
+                            break;
+                        case "A":
+                            path = ["C"][concat](a2c[apply](0, [d.x, d.y][concat](path.slice(1))));
+                            break;
+                        case "S":
+                            nx = d.x + (d.x - (d.bx || d.x));
+                            ny = d.y + (d.y - (d.by || d.y));
+                            path = ["C", nx, ny][concat](path.slice(1));
+                            break;
+                        case "T":
+                            d.qx = d.x + (d.x - (d.qx || d.x));
+                            d.qy = d.y + (d.y - (d.qy || d.y));
+                            path = ["C"][concat](q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
+                            break;
+                        case "Q":
+                            d.qx = path[1];
+                            d.qy = path[2];
+                            path = ["C"][concat](q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
+                            break;
+                        case "L":
+                            path = ["C"][concat](l2c(d.x, d.y, path[1], path[2]));
+                            break;
+                        case "H":
+                            path = ["C"][concat](l2c(d.x, d.y, path[1], d.y));
+                            break;
+                        case "V":
+                            path = ["C"][concat](l2c(d.x, d.y, d.x, path[1]));
+                            break;
+                        case "Z":
+                            path = ["C"][concat](l2c(d.x, d.y, d.X, d.Y));
+                            break;
+                    }
+                    return path;
+                },
+                fixArc = function (pp, i) {
+                    if (pp[i].length > 7) {
+                        pp[i].shift();
+                        var pi = pp[i];
+                        while (pi.length) {
+                            pp.splice(i++, 0, ["C"][concat](pi.splice(0, 6)));
+                        }
+                        pp.splice(i, 1);
+                        ii = mmax(p.length, p2 && p2.length || 0);
+                    }
+                },
+                fixM = function (path1, path2, a1, a2, i) {
+                    if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
+                        path2.splice(i, 0, ["M", a2.x, a2.y]);
+                        a1.bx = 0;
+                        a1.by = 0;
+                        a1.x = path1[i][1];
+                        a1.y = path1[i][2];
+                        ii = mmax(p.length, p2 && p2.length || 0);
+                    }
+                };
+            for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) {
+                p[i] = processPath(p[i], attrs);
+                fixArc(p, i);
+                p2 && (p2[i] = processPath(p2[i], attrs2));
+                p2 && fixArc(p2, i);
+                fixM(p, p2, attrs, attrs2, i);
+                fixM(p2, p, attrs2, attrs, i);
+                var seg = p[i],
+                    seg2 = p2 && p2[i],
+                    seglen = seg.length,
+                    seg2len = p2 && seg2.length;
+                attrs.x = seg[seglen - 2];
+                attrs.y = seg[seglen - 1];
+                attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
+                attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
+                attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
+                attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
+                attrs2.x = p2 && seg2[seg2len - 2];
+                attrs2.y = p2 && seg2[seg2len - 1];
+            }
+            if (!p2) {
+                pth.curve = pathClone(p);
+            }
+            return p2 ? [p, p2] : p;
+        }, null, pathClone),
+        parseDots = R._parseDots = cacher(function (gradient) {
+            var dots = [];
+            for (var i = 0, ii = gradient.length; i < ii; i++) {
+                var dot = {},
+                    par = gradient[i].match(/^([^:]*):?([\d\.]*)/);
+                dot.color = R.getRGB(par[1]);
+                if (dot.color.error) {
+                    return null;
+                }
+                dot.color = dot.color.hex;
+                par[2] && (dot.offset = par[2] + "%");
+                dots.push(dot);
+            }
+            for (i = 1, ii = dots.length - 1; i < ii; i++) {
+                if (!dots[i].offset) {
+                    var start = toFloat(dots[i - 1].offset || 0),
+                        end = 0;
+                    for (var j = i + 1; j < ii; j++) {
+                        if (dots[j].offset) {
+                            end = dots[j].offset;
+                            break;
+                        }
+                    }
+                    if (!end) {
+                        end = 100;
+                        j = ii;
+                    }
+                    end = toFloat(end);
+                    var d = (end - start) / (j - i + 1);
+                    for (; i < j; i++) {
+                        start += d;
+                        dots[i].offset = start + "%";
+                    }
+                }
+            }
+            return dots;
+        }),
+        tear = R._tear = function (el, paper) {
+            el == paper.top && (paper.top = el.prev);
+            el == paper.bottom && (paper.bottom = el.next);
+            el.next && (el.next.prev = el.prev);
+            el.prev && (el.prev.next = el.next);
+        },
+        tofront = R._tofront = function (el, paper) {
+            if (paper.top === el) {
+                return;
+            }
+            tear(el, paper);
+            el.next = null;
+            el.prev = paper.top;
+            paper.top.next = el;
+            paper.top = el;
+        },
+        toback = R._toback = function (el, paper) {
+            if (paper.bottom === el) {
+                return;
+            }
+            tear(el, paper);
+            el.next = paper.bottom;
+            el.prev = null;
+            paper.bottom.prev = el;
+            paper.bottom = el;
+        },
+        insertafter = R._insertafter = function (el, el2, paper) {
+            tear(el, paper);
+            el2 == paper.top && (paper.top = el);
+            el2.next && (el2.next.prev = el);
+            el.next = el2.next;
+            el.prev = el2;
+            el2.next = el;
+        },
+        insertbefore = R._insertbefore = function (el, el2, paper) {
+            tear(el, paper);
+            el2 == paper.bottom && (paper.bottom = el);
+            el2.prev && (el2.prev.next = el);
+            el.prev = el2.prev;
+            el2.prev = el;
+            el.next = el2;
+        },
+        
+        toMatrix = R.toMatrix = function (path, transform) {
+            var bb = pathDimensions(path),
+                el = {
+                    _: {
+                        transform: E
+                    },
+                    getBBox: function () {
+                        return bb;
+                    }
+                };
+            extractTransform(el, transform);
+            return el.matrix;
+        },
+        
+        transformPath = R.transformPath = function (path, transform) {
+            return mapPath(path, toMatrix(path, transform));
+        },
+        extractTransform = R._extractTransform = function (el, tstr) {
+            if (tstr == null) {
+                return el._.transform;
+            }
+            tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || E);
+            var tdata = R.parseTransformString(tstr),
+                deg = 0,
+                dx = 0,
+                dy = 0,
+                sx = 1,
+                sy = 1,
+                _ = el._,
+                m = new Matrix;
+            _.transform = tdata || [];
+            if (tdata) {
+                for (var i = 0, ii = tdata.length; i < ii; i++) {
+                    var t = tdata[i],
+                        tlen = t.length,
+                        command = Str(t[0]).toLowerCase(),
+                        absolute = t[0] != command,
+                        inver = absolute ? m.invert() : 0,
+                        x1,
+                        y1,
+                        x2,
+                        y2,
+                        bb;
+                    if (command == "t" && tlen == 3) {
+                        if (absolute) {
+                            x1 = inver.x(0, 0);
+                            y1 = inver.y(0, 0);
+                            x2 = inver.x(t[1], t[2]);
+                            y2 = inver.y(t[1], t[2]);
+                            m.translate(x2 - x1, y2 - y1);
+                        } else {
+                            m.translate(t[1], t[2]);
+                        }
+                    } else if (command == "r") {
+                        if (tlen == 2) {
+                            bb = bb || el.getBBox(1);
+                            m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                            deg += t[1];
+                        } else if (tlen == 4) {
+                            if (absolute) {
+                                x2 = inver.x(t[2], t[3]);
+                                y2 = inver.y(t[2], t[3]);
+                                m.rotate(t[1], x2, y2);
+                            } else {
+                                m.rotate(t[1], t[2], t[3]);
+                            }
+                            deg += t[1];
+                        }
+                    } else if (command == "s") {
+                        if (tlen == 2 || tlen == 3) {
+                            bb = bb || el.getBBox(1);
+                            m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                            sx *= t[1];
+                            sy *= t[tlen - 1];
+                        } else if (tlen == 5) {
+                            if (absolute) {
+                                x2 = inver.x(t[3], t[4]);
+                                y2 = inver.y(t[3], t[4]);
+                                m.scale(t[1], t[2], x2, y2);
+                            } else {
+                                m.scale(t[1], t[2], t[3], t[4]);
+                            }
+                            sx *= t[1];
+                            sy *= t[2];
+                        }
+                    } else if (command == "m" && tlen == 7) {
+                        m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
+                    }
+                    _.dirtyT = 1;
+                    el.matrix = m;
+                }
+            }
+
+            
+            el.matrix = m;
+
+            _.sx = sx;
+            _.sy = sy;
+            _.deg = deg;
+            _.dx = dx = m.e;
+            _.dy = dy = m.f;
+
+            if (sx == 1 && sy == 1 && !deg && _.bbox) {
+                _.bbox.x += +dx;
+                _.bbox.y += +dy;
+            } else {
+                _.dirtyT = 1;
+            }
+        },
+        getEmpty = function (item) {
+            var l = item[0];
+            switch (l.toLowerCase()) {
+                case "t": return [l, 0, 0];
+                case "m": return [l, 1, 0, 0, 1, 0, 0];
+                case "r": if (item.length == 4) {
+                    return [l, 0, item[2], item[3]];
+                } else {
+                    return [l, 0];
+                }
+                case "s": if (item.length == 5) {
+                    return [l, 1, 1, item[3], item[4]];
+                } else if (item.length == 3) {
+                    return [l, 1, 1];
+                } else {
+                    return [l, 1];
+                }
+            }
+        },
+        equaliseTransform = R._equaliseTransform = function (t1, t2) {
+            t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
+            t1 = R.parseTransformString(t1) || [];
+            t2 = R.parseTransformString(t2) || [];
+            var maxlength = mmax(t1.length, t2.length),
+                from = [],
+                to = [],
+                i = 0, j, jj,
+                tt1, tt2;
+            for (; i < maxlength; i++) {
+                tt1 = t1[i] || getEmpty(t2[i]);
+                tt2 = t2[i] || getEmpty(tt1);
+                if ((tt1[0] != tt2[0]) ||
+                    (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
+                    (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
+                    ) {
+                    return;
+                }
+                from[i] = [];
+                to[i] = [];
+                for (j = 0, jj = mmax(tt1.length, tt2.length); j < jj; j++) {
+                    j in tt1 && (from[i][j] = tt1[j]);
+                    j in tt2 && (to[i][j] = tt2[j]);
+                }
+            }
+            return {
+                from: from,
+                to: to
+            };
+        };
+    R._getContainer = function (x, y, w, h) {
+        var container;
+        container = h == null && !R.is(x, "object") ? g.doc.getElementById(x) : x;
+        if (container == null) {
+            return;
+        }
+        if (container.tagName) {
+            if (y == null) {
+                return {
+                    container: container,
+                    width: container.style.pixelWidth || container.offsetWidth,
+                    height: container.style.pixelHeight || container.offsetHeight
+                };
+            } else {
+                return {
+                    container: container,
+                    width: y,
+                    height: w
+                };
+            }
+        }
+        return {
+            container: 1,
+            x: x,
+            y: y,
+            width: w,
+            height: h
+        };
+    };
+    
+    R.pathToRelative = pathToRelative;
+    R._engine = {};
+    
+    R.path2curve = path2curve;
+    
+    R.matrix = function (a, b, c, d, e, f) {
+        return new Matrix(a, b, c, d, e, f);
+    };
+    function Matrix(a, b, c, d, e, f) {
+        if (a != null) {
+            this.a = +a;
+            this.b = +b;
+            this.c = +c;
+            this.d = +d;
+            this.e = +e;
+            this.f = +f;
+        } else {
+            this.a = 1;
+            this.b = 0;
+            this.c = 0;
+            this.d = 1;
+            this.e = 0;
+            this.f = 0;
+        }
+    }
+    (function (matrixproto) {
+        
+        matrixproto.add = function (a, b, c, d, e, f) {
+            var out = [[], [], []],
+                m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
+                matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
+                x, y, z, res;
+
+            if (a && a instanceof Matrix) {
+                matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
+            }
+
+            for (x = 0; x < 3; x++) {
+                for (y = 0; y < 3; y++) {
+                    res = 0;
+                    for (z = 0; z < 3; z++) {
+                        res += m[x][z] * matrix[z][y];
+                    }
+                    out[x][y] = res;
+                }
+            }
+            this.a = out[0][0];
+            this.b = out[1][0];
+            this.c = out[0][1];
+            this.d = out[1][1];
+            this.e = out[0][2];
+            this.f = out[1][2];
+        };
+        
+        matrixproto.invert = function () {
+            var me = this,
+                x = me.a * me.d - me.b * me.c;
+            return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
+        };
+        
+        matrixproto.clone = function () {
+            return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
+        };
+        
+        matrixproto.translate = function (x, y) {
+            this.add(1, 0, 0, 1, x, y);
+        };
+        
+        matrixproto.scale = function (x, y, cx, cy) {
+            y == null && (y = x);
+            (cx || cy) && this.add(1, 0, 0, 1, cx, cy);
+            this.add(x, 0, 0, y, 0, 0);
+            (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
+        };
+        
+        matrixproto.rotate = function (a, x, y) {
+            a = R.rad(a);
+            x = x || 0;
+            y = y || 0;
+            var cos = +math.cos(a).toFixed(9),
+                sin = +math.sin(a).toFixed(9);
+            this.add(cos, sin, -sin, cos, x, y);
+            this.add(1, 0, 0, 1, -x, -y);
+        };
+        
+        matrixproto.x = function (x, y) {
+            return x * this.a + y * this.c + this.e;
+        };
+        
+        matrixproto.y = function (x, y) {
+            return x * this.b + y * this.d + this.f;
+        };
+        matrixproto.get = function (i) {
+            return +this[Str.fromCharCode(97 + i)].toFixed(4);
+        };
+        matrixproto.toString = function () {
+            return R.svg ?
+                "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")" :
+                [this.get(0), this.get(2), this.get(1), this.get(3), 0, 0].join();
+        };
+        matrixproto.toFilter = function () {
+            return "progid:DXImageTransform.Microsoft.Matrix(M11=" + this.get(0) +
+                ", M12=" + this.get(2) + ", M21=" + this.get(1) + ", M22=" + this.get(3) +
+                ", Dx=" + this.get(4) + ", Dy=" + this.get(5) + ", sizingmethod='auto expand')";
+        };
+        matrixproto.offset = function () {
+            return [this.e.toFixed(4), this.f.toFixed(4)];
+        };
+        function norm(a) {
+            return a[0] * a[0] + a[1] * a[1];
+        }
+        function normalize(a) {
+            var mag = math.sqrt(norm(a));
+            a[0] && (a[0] /= mag);
+            a[1] && (a[1] /= mag);
+        }
+        
+        matrixproto.split = function () {
+            var out = {};
+            // translation
+            out.dx = this.e;
+            out.dy = this.f;
+
+            // scale and shear
+            var row = [[this.a, this.c], [this.b, this.d]];
+            out.scalex = math.sqrt(norm(row[0]));
+            normalize(row[0]);
+
+            out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
+            row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
+
+            out.scaley = math.sqrt(norm(row[1]));
+            normalize(row[1]);
+            out.shear /= out.scaley;
+
+            // rotation
+            var sin = -row[0][1],
+                cos = row[1][1];
+            if (cos < 0) {
+                out.rotate = R.deg(math.acos(cos));
+                if (sin < 0) {
+                    out.rotate = 360 - out.rotate;
+                }
+            } else {
+                out.rotate = R.deg(math.asin(sin));
+            }
+
+            out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
+            out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
+            out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
+            return out;
+        };
+        
+        matrixproto.toTransformString = function (shorter) {
+            var s = shorter || this[split]();
+            if (s.isSimple) {
+                s.scalex = +s.scalex.toFixed(4);
+                s.scaley = +s.scaley.toFixed(4);
+                s.rotate = +s.rotate.toFixed(4);
+                return  (s.dx || s.dy ? "t" + [s.dx, s.dy] : E) + 
+                        (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
+                        (s.rotate ? "r" + [s.rotate, 0, 0] : E);
+            } else {
+                return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
+            }
+        };
+    })(Matrix.prototype);
+
+    // WebKit rendering bug workaround method
+    var version = navigator.userAgent.match(/Version\/(.*?)\s/) || navigator.userAgent.match(/Chrome\/(\d+)/);
+    if ((navigator.vendor == "Apple Computer, Inc.") && (version && version[1] < 4 || navigator.platform.slice(0, 2) == "iP") ||
+        (navigator.vendor == "Google Inc." && version && version[1] < 8)) {
+        
+        paperproto.safari = function () {
+            var rect = this.rect(-99, -99, this.width + 99, this.height + 99).attr({stroke: "none"});
+            setTimeout(function () {rect.remove();});
+        };
+    } else {
+        paperproto.safari = fun;
+    }
+ 
+    var preventDefault = function () {
+        this.returnValue = false;
+    },
+    preventTouch = function () {
+        return this.originalEvent.preventDefault();
+    },
+    stopPropagation = function () {
+        this.cancelBubble = true;
+    },
+    stopTouch = function () {
+        return this.originalEvent.stopPropagation();
+    },
+    addEvent = (function () {
+        if (g.doc.addEventListener) {
+            return function (obj, type, fn, element) {
+                var realName = supportsTouch && touchMap[type] ? touchMap[type] : type,
+                    f = function (e) {
+                        var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
+                            scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
+                            x = e.clientX + scrollX,
+                            y = e.clientY + scrollY;
+                    if (supportsTouch && touchMap[has](type)) {
+                        for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
+                            if (e.targetTouches[i].target == obj) {
+                                var olde = e;
+                                e = e.targetTouches[i];
+                                e.originalEvent = olde;
+                                e.preventDefault = preventTouch;
+                                e.stopPropagation = stopTouch;
+                                break;
+                            }
+                        }
+                    }
+                    return fn.call(element, e, x, y);
+                };
+                obj.addEventListener(realName, f, false);
+                return function () {
+                    obj.removeEventListener(realName, f, false);
+                    return true;
+                };
+            };
+        } else if (g.doc.attachEvent) {
+            return function (obj, type, fn, element) {
+                var f = function (e) {
+                    e = e || g.win.event;
+                    var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
+                        scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
+                        x = e.clientX + scrollX,
+                        y = e.clientY + scrollY;
+                    e.preventDefault = e.preventDefault || preventDefault;
+                    e.stopPropagation = e.stopPropagation || stopPropagation;
+                    return fn.call(element, e, x, y);
+                };
+                obj.attachEvent("on" + type, f);
+                var detacher = function () {
+                    obj.detachEvent("on" + type, f);
+                    return true;
+                };
+                return detacher;
+            };
+        }
+    })(),
+    drag = [],
+    dragMove = function (e) {
+        var x = e.clientX,
+            y = e.clientY,
+            scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
+            scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
+            dragi,
+            j = drag.length;
+        while (j--) {
+            dragi = drag[j];
+            if (supportsTouch) {
+                var i = e.touches.length,
+                    touch;
+                while (i--) {
+                    touch = e.touches[i];
+                    if (touch.identifier == dragi.el._drag.id) {
+                        x = touch.clientX;
+                        y = touch.clientY;
+                        (e.originalEvent ? e.originalEvent : e).preventDefault();
+                        break;
+                    }
+                }
+            } else {
+                e.preventDefault();
+            }
+            var node = dragi.el.node,
+                o,
+                next = node.nextSibling,
+                parent = node.parentNode,
+                display = node.style.display;
+            g.win.opera && parent.removeChild(node);
+            node.style.display = "none";
+            o = dragi.el.paper.getElementByPoint(x, y);
+            node.style.display = display;
+            g.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
+            o && eve("raphael.drag.over." + dragi.el.id, dragi.el, o);
+            x += scrollX;
+            y += scrollY;
+            eve("raphael.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
+        }
+    },
+    dragUp = function (e) {
+        R.unmousemove(dragMove).unmouseup(dragUp);
+        var i = drag.length,
+            dragi;
+        while (i--) {
+            dragi = drag[i];
+            dragi.el._drag = {};
+            eve("raphael.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
+        }
+        drag = [];
+    },
+    
+    elproto = R.el = {};
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    for (var i = events.length; i--;) {
+        (function (eventName) {
+            R[eventName] = elproto[eventName] = function (fn, scope) {
+                if (R.is(fn, "function")) {
+                    this.events = this.events || [];
+                    this.events.push({name: eventName, f: fn, unbind: addEvent(this.shape || this.node || g.doc, eventName, fn, scope || this)});
+                }
+                return this;
+            };
+            R["un" + eventName] = elproto["un" + eventName] = function (fn) {
+                var events = this.events || [],
+                    l = events.length;
+                while (l--) if (events[l].name == eventName && events[l].f == fn) {
+                    events[l].unbind();
+                    events.splice(l, 1);
+                    !events.length && delete this.events;
+                    return this;
+                }
+                return this;
+            };
+        })(events[i]);
+    }
+    
+    
+    elproto.data = function (key, value) {
+        var data = eldata[this.id] = eldata[this.id] || {};
+        if (arguments.length == 1) {
+            if (R.is(key, "object")) {
+                for (var i in key) if (key[has](i)) {
+                    this.data(i, key[i]);
+                }
+                return this;
+            }
+            eve("raphael.data.get." + this.id, this, data[key], key);
+            return data[key];
+        }
+        data[key] = value;
+        eve("raphael.data.set." + this.id, this, value, key);
+        return this;
+    };
+    
+    elproto.removeData = function (key) {
+        if (key == null) {
+            eldata[this.id] = {};
+        } else {
+            eldata[this.id] && delete eldata[this.id][key];
+        }
+        return this;
+    };
+    
+    elproto.hover = function (f_in, f_out, scope_in, scope_out) {
+        return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
+    };
+    
+    elproto.unhover = function (f_in, f_out) {
+        return this.unmouseover(f_in).unmouseout(f_out);
+    };
+    var draggable = [];
+    
+    elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
+        function start(e) {
+            (e.originalEvent || e).preventDefault();
+            var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
+                scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft;
+            this._drag.x = e.clientX + scrollX;
+            this._drag.y = e.clientY + scrollY;
+            this._drag.id = e.identifier;
+            !drag.length && R.mousemove(dragMove).mouseup(dragUp);
+            drag.push({el: this, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
+            onstart && eve.on("raphael.drag.start." + this.id, onstart);
+            onmove && eve.on("raphael.drag.move." + this.id, onmove);
+            onend && eve.on("raphael.drag.end." + this.id, onend);
+            eve("raphael.drag.start." + this.id, start_scope || move_scope || this, e.clientX + scrollX, e.clientY + scrollY, e);
+        }
+        this._drag = {};
+        draggable.push({el: this, start: start});
+        this.mousedown(start);
+        return this;
+    };
+    
+    elproto.onDragOver = function (f) {
+        f ? eve.on("raphael.drag.over." + this.id, f) : eve.unbind("raphael.drag.over." + this.id);
+    };
+    
+    elproto.undrag = function () {
+        var i = draggable.length;
+        while (i--) if (draggable[i].el == this) {
+            this.unmousedown(draggable[i].start);
+            draggable.splice(i, 1);
+            eve.unbind("raphael.drag.*." + this.id);
+        }
+        !draggable.length && R.unmousemove(dragMove).unmouseup(dragUp);
+    };
+    
+    paperproto.circle = function (x, y, r) {
+        var out = R._engine.circle(this, x || 0, y || 0, r || 0);
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    
+    paperproto.rect = function (x, y, w, h, r) {
+        var out = R._engine.rect(this, x || 0, y || 0, w || 0, h || 0, r || 0);
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    
+    paperproto.ellipse = function (x, y, rx, ry) {
+        var out = R._engine.ellipse(this, x || 0, y || 0, rx || 0, ry || 0);
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    
+    paperproto.path = function (pathString) {
+        pathString && !R.is(pathString, string) && !R.is(pathString[0], array) && (pathString += E);
+        var out = R._engine.path(R.format[apply](R, arguments), this);
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    
+    paperproto.image = function (src, x, y, w, h) {
+        var out = R._engine.image(this, src || "about:blank", x || 0, y || 0, w || 0, h || 0);
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    
+    paperproto.text = function (x, y, text) {
+        var out = R._engine.text(this, x || 0, y || 0, Str(text));
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    
+    paperproto.set = function (itemsArray) {
+        !R.is(itemsArray, "array") && (itemsArray = Array.prototype.splice.call(arguments, 0, arguments.length));
+        var out = new Set(itemsArray);
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    
+    paperproto.setStart = function (set) {
+        this.__set__ = set || this.set();
+    };
+    
+    paperproto.setFinish = function (set) {
+        var out = this.__set__;
+        delete this.__set__;
+        return out;
+    };
+    
+    paperproto.setSize = function (width, height) {
+        return R._engine.setSize.call(this, width, height);
+    };
+    
+    paperproto.setViewBox = function (x, y, w, h, fit) {
+        return R._engine.setViewBox.call(this, x, y, w, h, fit);
+    };
+    
+    
+    paperproto.top = paperproto.bottom = null;
+    
+    paperproto.raphael = R;
+    var getOffset = function (elem) {
+        var box = elem.getBoundingClientRect(),
+            doc = elem.ownerDocument,
+            body = doc.body,
+            docElem = doc.documentElement,
+            clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
+            top  = box.top  + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop,
+            left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
+        return {
+            y: top,
+            x: left
+        };
+    };
+    
+    paperproto.getElementByPoint = function (x, y) {
+        var paper = this,
+            svg = paper.canvas,
+            target = g.doc.elementFromPoint(x, y);
+        if (g.win.opera && target.tagName == "svg") {
+            var so = getOffset(svg),
+                sr = svg.createSVGRect();
+            sr.x = x - so.x;
+            sr.y = y - so.y;
+            sr.width = sr.height = 1;
+            var hits = svg.getIntersectionList(sr, null);
+            if (hits.length) {
+                target = hits[hits.length - 1];
+            }
+        }
+        if (!target) {
+            return null;
+        }
+        while (target.parentNode && target != svg.parentNode && !target.raphael) {
+            target = target.parentNode;
+        }
+        target == paper.canvas.parentNode && (target = svg);
+        target = target && target.raphael ? paper.getById(target.raphaelid) : null;
+        return target;
+    };
+    
+    paperproto.getById = function (id) {
+        var bot = this.bottom;
+        while (bot) {
+            if (bot.id == id) {
+                return bot;
+            }
+            bot = bot.next;
+        }
+        return null;
+    };
+    
+    paperproto.forEach = function (callback, thisArg) {
+        var bot = this.bottom;
+        while (bot) {
+            if (callback.call(thisArg, bot) === false) {
+                return this;
+            }
+            bot = bot.next;
+        }
+        return this;
+    };
+    
+    paperproto.getElementsByPoint = function (x, y) {
+        var set = this.set();
+        this.forEach(function (el) {
+            if (el.isPointInside(x, y)) {
+                set.push(el);
+            }
+        });
+        return set;
+    };
+    function x_y() {
+        return this.x + S + this.y;
+    }
+    function x_y_w_h() {
+        return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
+    }
+    
+    elproto.isPointInside = function (x, y) {
+        var rp = this.realPath = this.realPath || getPath[this.type](this);
+        return R.isPointInsidePath(rp, x, y);
+    };
+    
+    elproto.getBBox = function (isWithoutTransform) {
+        if (this.removed) {
+            return {};
+        }
+        var _ = this._;
+        if (isWithoutTransform) {
+            if (_.dirty || !_.bboxwt) {
+                this.realPath = getPath[this.type](this);
+                _.bboxwt = pathDimensions(this.realPath);
+                _.bboxwt.toString = x_y_w_h;
+                _.dirty = 0;
+            }
+            return _.bboxwt;
+        }
+        if (_.dirty || _.dirtyT || !_.bbox) {
+            if (_.dirty || !this.realPath) {
+                _.bboxwt = 0;
+                this.realPath = getPath[this.type](this);
+            }
+            _.bbox = pathDimensions(mapPath(this.realPath, this.matrix));
+            _.bbox.toString = x_y_w_h;
+            _.dirty = _.dirtyT = 0;
+        }
+        return _.bbox;
+    };
+    
+    elproto.clone = function () {
+        if (this.removed) {
+            return null;
+        }
+        var out = this.paper[this.type]().attr(this.attr());
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    
+    elproto.glow = function (glow) {
+        if (this.type == "text") {
+            return null;
+        }
+        glow = glow || {};
+        var s = {
+            width: (glow.width || 10) + (+this.attr("stroke-width") || 1),
+            fill: glow.fill || false,
+            opacity: glow.opacity || .5,
+            offsetx: glow.offsetx || 0,
+            offsety: glow.offsety || 0,
+            color: glow.color || "#000"
+        },
+            c = s.width / 2,
+            r = this.paper,
+            out = r.set(),
+            path = this.realPath || getPath[this.type](this);
+        path = this.matrix ? mapPath(path, this.matrix) : path;
+        for (var i = 1; i < c + 1; i++) {
+            out.push(r.path(path).attr({
+                stroke: s.color,
+                fill: s.fill ? s.color : "none",
+                "stroke-linejoin": "round",
+                "stroke-linecap": "round",
+                "stroke-width": +(s.width / c * i).toFixed(3),
+                opacity: +(s.opacity / c).toFixed(3)
+            }));
+        }
+        return out.insertBefore(this).translate(s.offsetx, s.offsety);
+    };
+    var curveslengths = {},
+    getPointAtSegmentLength = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
+        if (length == null) {
+            return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
+        } else {
+            return R.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, getTatLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
+        }
+    },
+    getLengthFactory = function (istotal, subpath) {
+        return function (path, length, onlystart) {
+            path = path2curve(path);
+            var x, y, p, l, sp = "", subpaths = {}, point,
+                len = 0;
+            for (var i = 0, ii = path.length; i < ii; i++) {
+                p = path[i];
+                if (p[0] == "M") {
+                    x = +p[1];
+                    y = +p[2];
+                } else {
+                    l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                    if (len + l > length) {
+                        if (subpath && !subpaths.start) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            sp += ["C" + point.start.x, point.start.y, point.m.x, point.m.y, point.x, point.y];
+                            if (onlystart) {return sp;}
+                            subpaths.start = sp;
+                            sp = ["M" + point.x, point.y + "C" + point.n.x, point.n.y, point.end.x, point.end.y, p[5], p[6]].join();
+                            len += l;
+                            x = +p[5];
+                            y = +p[6];
+                            continue;
+                        }
+                        if (!istotal && !subpath) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            return {x: point.x, y: point.y, alpha: point.alpha};
+                        }
+                    }
+                    len += l;
+                    x = +p[5];
+                    y = +p[6];
+                }
+                sp += p.shift() + p;
+            }
+            subpaths.end = sp;
+            point = istotal ? len : subpath ? subpaths : R.findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
+            point.alpha && (point = {x: point.x, y: point.y, alpha: point.alpha});
+            return point;
+        };
+    };
+    var getTotalLength = getLengthFactory(1),
+        getPointAtLength = getLengthFactory(),
+        getSubpathsAtLength = getLengthFactory(0, 1);
+    
+    R.getTotalLength = getTotalLength;
+    
+    R.getPointAtLength = getPointAtLength;
+    
+    R.getSubpath = function (path, from, to) {
+        if (this.getTotalLength(path) - to < 1e-6) {
+            return getSubpathsAtLength(path, from).end;
+        }
+        var a = getSubpathsAtLength(path, to, 1);
+        return from ? getSubpathsAtLength(a, from).end : a;
+    };
+    
+    elproto.getTotalLength = function () {
+        if (this.type != "path") {return;}
+        if (this.node.getTotalLength) {
+            return this.node.getTotalLength();
+        }
+        return getTotalLength(this.attrs.path);
+    };
+    
+    elproto.getPointAtLength = function (length) {
+        if (this.type != "path") {return;}
+        return getPointAtLength(this.attrs.path, length);
+    };
+    
+    elproto.getSubpath = function (from, to) {
+        if (this.type != "path") {return;}
+        return R.getSubpath(this.attrs.path, from, to);
+    };
+    
+    var ef = R.easing_formulas = {
+        linear: function (n) {
+            return n;
+        },
+        "<": function (n) {
+            return pow(n, 1.7);
+        },
+        ">": function (n) {
+            return pow(n, .48);
+        },
+        "<>": function (n) {
+            var q = .48 - n / 1.04,
+                Q = math.sqrt(.1734 + q * q),
+                x = Q - q,
+                X = pow(abs(x), 1 / 3) * (x < 0 ? -1 : 1),
+                y = -Q - q,
+                Y = pow(abs(y), 1 / 3) * (y < 0 ? -1 : 1),
+                t = X + Y + .5;
+            return (1 - t) * 3 * t * t + t * t * t;
+        },
+        backIn: function (n) {
+            var s = 1.70158;
+            return n * n * ((s + 1) * n - s);
+        },
+        backOut: function (n) {
+            n = n - 1;
+            var s = 1.70158;
+            return n * n * ((s + 1) * n + s) + 1;
+        },
+        elastic: function (n) {
+            if (n == !!n) {
+                return n;
+            }
+            return pow(2, -10 * n) * math.sin((n - .075) * (2 * PI) / .3) + 1;
+        },
+        bounce: function (n) {
+            var s = 7.5625,
+                p = 2.75,
+                l;
+            if (n < (1 / p)) {
+                l = s * n * n;
+            } else {
+                if (n < (2 / p)) {
+                    n -= (1.5 / p);
+                    l = s * n * n + .75;
+                } else {
+                    if (n < (2.5 / p)) {
+                        n -= (2.25 / p);
+                        l = s * n * n + .9375;
+                    } else {
+                        n -= (2.625 / p);
+                        l = s * n * n + .984375;
+                    }
+                }
+            }
+            return l;
+        }
+    };
+    ef.easeIn = ef["ease-in"] = ef["<"];
+    ef.easeOut = ef["ease-out"] = ef[">"];
+    ef.easeInOut = ef["ease-in-out"] = ef["<>"];
+    ef["back-in"] = ef.backIn;
+    ef["back-out"] = ef.backOut;
+
+    var animationElements = [],
+        requestAnimFrame = window.requestAnimationFrame       ||
+                           window.webkitRequestAnimationFrame ||
+                           window.mozRequestAnimationFrame    ||
+                           window.oRequestAnimationFrame      ||
+                           window.msRequestAnimationFrame     ||
+                           function (callback) {
+                               setTimeout(callback, 16);
+                           },
+        animation = function () {
+            var Now = +new Date,
+                l = 0;
+            for (; l < animationElements.length; l++) {
+                var e = animationElements[l];
+                if (e.el.removed || e.paused) {
+                    continue;
+                }
+                var time = Now - e.start,
+                    ms = e.ms,
+                    easing = e.easing,
+                    from = e.from,
+                    diff = e.diff,
+                    to = e.to,
+                    t = e.t,
+                    that = e.el,
+                    set = {},
+                    now,
+                    init = {},
+                    key;
+                if (e.initstatus) {
+                    time = (e.initstatus * e.anim.top - e.prev) / (e.percent - e.prev) * ms;
+                    e.status = e.initstatus;
+                    delete e.initstatus;
+                    e.stop && animationElements.splice(l--, 1);
+                } else {
+                    e.status = (e.prev + (e.percent - e.prev) * (time / ms)) / e.anim.top;
+                }
+                if (time < 0) {
+                    continue;
+                }
+                if (time < ms) {
+                    var pos = easing(time / ms);
+                    for (var attr in from) if (from[has](attr)) {
+                        switch (availableAnimAttrs[attr]) {
+                            case nu:
+                                now = +from[attr] + pos * ms * diff[attr];
+                                break;
+                            case "colour":
+                                now = "rgb(" + [
+                                    upto255(round(from[attr].r + pos * ms * diff[attr].r)),
+                                    upto255(round(from[attr].g + pos * ms * diff[attr].g)),
+                                    upto255(round(from[attr].b + pos * ms * diff[attr].b))
+                                ].join(",") + ")";
+                                break;
+                            case "path":
+                                now = [];
+                                for (var i = 0, ii = from[attr].length; i < ii; i++) {
+                                    now[i] = [from[attr][i][0]];
+                                    for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
+                                        now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j];
+                                    }
+                                    now[i] = now[i].join(S);
+                                }
+                                now = now.join(S);
+                                break;
+                            case "transform":
+                                if (diff[attr].real) {
+                                    now = [];
+                                    for (i = 0, ii = from[attr].length; i < ii; i++) {
+                                        now[i] = [from[attr][i][0]];
+                                        for (j = 1, jj = from[attr][i].length; j < jj; j++) {
+                                            now[i][j] = from[attr][i][j] + pos * ms * diff[attr][i][j];
+                                        }
+                                    }
+                                } else {
+                                    var get = function (i) {
+                                        return +from[attr][i] + pos * ms * diff[attr][i];
+                                    };
+                                    // now = [["r", get(2), 0, 0], ["t", get(3), get(4)], ["s", get(0), get(1), 0, 0]];
+                                    now = [["m", get(0), get(1), get(2), get(3), get(4), get(5)]];
+                                }
+                                break;
+                            case "csv":
+                                if (attr == "clip-rect") {
+                                    now = [];
+                                    i = 4;
+                                    while (i--) {
+                                        now[i] = +from[attr][i] + pos * ms * diff[attr][i];
+                                    }
+                                }
+                                break;
+                            default:
+                                var from2 = [][concat](from[attr]);
+                                now = [];
+                                i = that.paper.customAttributes[attr].length;
+                                while (i--) {
+                                    now[i] = +from2[i] + pos * ms * diff[attr][i];
+                                }
+                                break;
+                        }
+                        set[attr] = now;
+                    }
+                    that.attr(set);
+                    (function (id, that, anim) {
+                        setTimeout(function () {
+                            eve("raphael.anim.frame." + id, that, anim);
+                        });
+                    })(that.id, that, e.anim);
+                } else {
+                    (function(f, el, a) {
+                        setTimeout(function() {
+                            eve("raphael.anim.frame." + el.id, el, a);
+                            eve("raphael.anim.finish." + el.id, el, a);
+                            R.is(f, "function") && f.call(el);
+                        });
+                    })(e.callback, that, e.anim);
+                    that.attr(to);
+                    animationElements.splice(l--, 1);
+                    if (e.repeat > 1 && !e.next) {
+                        for (key in to) if (to[has](key)) {
+                            init[key] = e.totalOrigin[key];
+                        }
+                        e.el.attr(init);
+                        runAnimation(e.anim, e.el, e.anim.percents[0], null, e.totalOrigin, e.repeat - 1);
+                    }
+                    if (e.next && !e.stop) {
+                        runAnimation(e.anim, e.el, e.next, null, e.totalOrigin, e.repeat);
+                    }
+                }
+            }
+            R.svg && that && that.paper && that.paper.safari();
+            animationElements.length && requestAnimFrame(animation);
+        },
+        upto255 = function (color) {
+            return color > 255 ? 255 : color < 0 ? 0 : color;
+        };
+    
+    elproto.animateWith = function (el, anim, params, ms, easing, callback) {
+        var element = this;
+        if (element.removed) {
+            callback && callback.call(element);
+            return element;
+        }
+        var a = params instanceof Animation ? params : R.animation(params, ms, easing, callback),
+            x, y;
+        runAnimation(a, element, a.percents[0], null, element.attr());
+        for (var i = 0, ii = animationElements.length; i < ii; i++) {
+            if (animationElements[i].anim == anim && animationElements[i].el == el) {
+                animationElements[ii - 1].start = animationElements[i].start;
+                break;
+            }
+        }
+        return element;
+        // 
+        // 
+        // var a = params ? R.animation(params, ms, easing, callback) : anim,
+        //     status = element.status(anim);
+        // return this.animate(a).status(a, status * anim.ms / a.ms);
+    };
+    function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration) {
+        var cx = 3 * p1x,
+            bx = 3 * (p2x - p1x) - cx,
+            ax = 1 - cx - bx,
+            cy = 3 * p1y,
+            by = 3 * (p2y - p1y) - cy,
+            ay = 1 - cy - by;
+        function sampleCurveX(t) {
+            return ((ax * t + bx) * t + cx) * t;
+        }
+        function solve(x, epsilon) {
+            var t = solveCurveX(x, epsilon);
+            return ((ay * t + by) * t + cy) * t;
+        }
+        function solveCurveX(x, epsilon) {
+            var t0, t1, t2, x2, d2, i;
+            for(t2 = x, i = 0; i < 8; i++) {
+                x2 = sampleCurveX(t2) - x;
+                if (abs(x2) < epsilon) {
+                    return t2;
+                }
+                d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
+                if (abs(d2) < 1e-6) {
+                    break;
+                }
+                t2 = t2 - x2 / d2;
+            }
+            t0 = 0;
+            t1 = 1;
+            t2 = x;
+            if (t2 < t0) {
+                return t0;
+            }
+            if (t2 > t1) {
+                return t1;
+            }
+            while (t0 < t1) {
+                x2 = sampleCurveX(t2);
+                if (abs(x2 - x) < epsilon) {
+                    return t2;
+                }
+                if (x > x2) {
+                    t0 = t2;
+                } else {
+                    t1 = t2;
+                }
+                t2 = (t1 - t0) / 2 + t0;
+            }
+            return t2;
+        }
+        return solve(t, 1 / (200 * duration));
+    }
+    elproto.onAnimation = function (f) {
+        f ? eve.on("raphael.anim.frame." + this.id, f) : eve.unbind("raphael.anim.frame." + this.id);
+        return this;
+    };
+    function Animation(anim, ms) {
+        var percents = [],
+            newAnim = {};
+        this.ms = ms;
+        this.times = 1;
+        if (anim) {
+            for (var attr in anim) if (anim[has](attr)) {
+                newAnim[toFloat(attr)] = anim[attr];
+                percents.push(toFloat(attr));
+            }
+            percents.sort(sortByNumber);
+        }
+        this.anim = newAnim;
+        this.top = percents[percents.length - 1];
+        this.percents = percents;
+    }
+    
+    Animation.prototype.delay = function (delay) {
+        var a = new Animation(this.anim, this.ms);
+        a.times = this.times;
+        a.del = +delay || 0;
+        return a;
+    };
+    
+    Animation.prototype.repeat = function (times) { 
+        var a = new Animation(this.anim, this.ms);
+        a.del = this.del;
+        a.times = math.floor(mmax(times, 0)) || 1;
+        return a;
+    };
+    function runAnimation(anim, element, percent, status, totalOrigin, times) {
+        percent = toFloat(percent);
+        var params,
+            isInAnim,
+            isInAnimSet,
+            percents = [],
+            next,
+            prev,
+            timestamp,
+            ms = anim.ms,
+            from = {},
+            to = {},
+            diff = {};
+        if (status) {
+            for (i = 0, ii = animationElements.length; i < ii; i++) {
+                var e = animationElements[i];
+                if (e.el.id == element.id && e.anim == anim) {
+                    if (e.percent != percent) {
+                        animationElements.splice(i, 1);
+                        isInAnimSet = 1;
+                    } else {
+                        isInAnim = e;
+                    }
+                    element.attr(e.totalOrigin);
+                    break;
+                }
+            }
+        } else {
+            status = +to; // NaN
+        }
+        for (var i = 0, ii = anim.percents.length; i < ii; i++) {
+            if (anim.percents[i] == percent || anim.percents[i] > status * anim.top) {
+                percent = anim.percents[i];
+                prev = anim.percents[i - 1] || 0;
+                ms = ms / anim.top * (percent - prev);
+                next = anim.percents[i + 1];
+                params = anim.anim[percent];
+                break;
+            } else if (status) {
+                element.attr(anim.anim[anim.percents[i]]);
+            }
+        }
+        if (!params) {
+            return;
+        }
+        if (!isInAnim) {
+            for (var attr in params) if (params[has](attr)) {
+                if (availableAnimAttrs[has](attr) || element.paper.customAttributes[has](attr)) {
+                    from[attr] = element.attr(attr);
+                    (from[attr] == null) && (from[attr] = availableAttrs[attr]);
+                    to[attr] = params[attr];
+                    switch (availableAnimAttrs[attr]) {
+                        case nu:
+                            diff[attr] = (to[attr] - from[attr]) / ms;
+                            break;
+                        case "colour":
+                            from[attr] = R.getRGB(from[attr]);
+                            var toColour = R.getRGB(to[attr]);
+                            diff[attr] = {
+                                r: (toColour.r - from[attr].r) / ms,
+                                g: (toColour.g - from[attr].g) / ms,
+                                b: (toColour.b - from[attr].b) / ms
+                            };
+                            break;
+                        case "path":
+                            var pathes = path2curve(from[attr], to[attr]),
+                                toPath = pathes[1];
+                            from[attr] = pathes[0];
+                            diff[attr] = [];
+                            for (i = 0, ii = from[attr].length; i < ii; i++) {
+                                diff[attr][i] = [0];
+                                for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
+                                    diff[attr][i][j] = (toPath[i][j] - from[attr][i][j]) / ms;
+                                }
+                            }
+                            break;
+                        case "transform":
+                            var _ = element._,
+                                eq = equaliseTransform(_[attr], to[attr]);
+                            if (eq) {
+                                from[attr] = eq.from;
+                                to[attr] = eq.to;
+                                diff[attr] = [];
+                                diff[attr].real = true;
+                                for (i = 0, ii = from[attr].length; i < ii; i++) {
+                                    diff[attr][i] = [from[attr][i][0]];
+                                    for (j = 1, jj = from[attr][i].length; j < jj; j++) {
+                                        diff[attr][i][j] = (to[attr][i][j] - from[attr][i][j]) / ms;
+                                    }
+                                }
+                            } else {
+                                var m = (element.matrix || new Matrix),
+                                    to2 = {
+                                        _: {transform: _.transform},
+                                        getBBox: function () {
+                                            return element.getBBox(1);
+                                        }
+                                    };
+                                from[attr] = [
+                                    m.a,
+                                    m.b,
+                                    m.c,
+                                    m.d,
+                                    m.e,
+                                    m.f
+                                ];
+                                extractTransform(to2, to[attr]);
+                                to[attr] = to2._.transform;
+                                diff[attr] = [
+                                    (to2.matrix.a - m.a) / ms,
+                                    (to2.matrix.b - m.b) / ms,
+                                    (to2.matrix.c - m.c) / ms,
+                                    (to2.matrix.d - m.d) / ms,
+                                    (to2.matrix.e - m.e) / ms,
+                                    (to2.matrix.f - m.f) / ms
+                                ];
+                                // from[attr] = [_.sx, _.sy, _.deg, _.dx, _.dy];
+                                // var to2 = {_:{}, getBBox: function () { return element.getBBox(); }};
+                                // extractTransform(to2, to[attr]);
+                                // diff[attr] = [
+                                //     (to2._.sx - _.sx) / ms,
+                                //     (to2._.sy - _.sy) / ms,
+                                //     (to2._.deg - _.deg) / ms,
+                                //     (to2._.dx - _.dx) / ms,
+                                //     (to2._.dy - _.dy) / ms
+                                // ];
+                            }
+                            break;
+                        case "csv":
+                            var values = Str(params[attr])[split](separator),
+                                from2 = Str(from[attr])[split](separator);
+                            if (attr == "clip-rect") {
+                                from[attr] = from2;
+                                diff[attr] = [];
+                                i = from2.length;
+                                while (i--) {
+                                    diff[attr][i] = (values[i] - from[attr][i]) / ms;
+                                }
+                            }
+                            to[attr] = values;
+                            break;
+                        default:
+                            values = [][concat](params[attr]);
+                            from2 = [][concat](from[attr]);
+                            diff[attr] = [];
+                            i = element.paper.customAttributes[attr].length;
+                            while (i--) {
+                                diff[attr][i] = ((values[i] || 0) - (from2[i] || 0)) / ms;
+                            }
+                            break;
+                    }
+                }
+            }
+            var easing = params.easing,
+                easyeasy = R.easing_formulas[easing];
+            if (!easyeasy) {
+                easyeasy = Str(easing).match(bezierrg);
+                if (easyeasy && easyeasy.length == 5) {
+                    var curve = easyeasy;
+                    easyeasy = function (t) {
+                        return CubicBezierAtTime(t, +curve[1], +curve[2], +curve[3], +curve[4], ms);
+                    };
+                } else {
+                    easyeasy = pipe;
+                }
+            }
+            timestamp = params.start || anim.start || +new Date;
+            e = {
+                anim: anim,
+                percent: percent,
+                timestamp: timestamp,
+                start: timestamp + (anim.del || 0),
+                status: 0,
+                initstatus: status || 0,
+                stop: false,
+                ms: ms,
+                easing: easyeasy,
+                from: from,
+                diff: diff,
+                to: to,
+                el: element,
+                callback: params.callback,
+                prev: prev,
+                next: next,
+                repeat: times || anim.times,
+                origin: element.attr(),
+                totalOrigin: totalOrigin
+            };
+            animationElements.push(e);
+            if (status && !isInAnim && !isInAnimSet) {
+                e.stop = true;
+                e.start = new Date - ms * status;
+                if (animationElements.length == 1) {
+                    return animation();
+                }
+            }
+            if (isInAnimSet) {
+                e.start = new Date - e.ms * status;
+            }
+            animationElements.length == 1 && requestAnimFrame(animation);
+        } else {
+            isInAnim.initstatus = status;
+            isInAnim.start = new Date - isInAnim.ms * status;
+        }
+        eve("raphael.anim.start." + element.id, element, anim);
+    }
+    
+    R.animation = function (params, ms, easing, callback) {
+        if (params instanceof Animation) {
+            return params;
+        }
+        if (R.is(easing, "function") || !easing) {
+            callback = callback || easing || null;
+            easing = null;
+        }
+        params = Object(params);
+        ms = +ms || 0;
+        var p = {},
+            json,
+            attr;
+        for (attr in params) if (params[has](attr) && toFloat(attr) != attr && toFloat(attr) + "%" != attr) {
+            json = true;
+            p[attr] = params[attr];
+        }
+        if (!json) {
+            return new Animation(params, ms);
+        } else {
+            easing && (p.easing = easing);
+            callback && (p.callback = callback);
+            return new Animation({100: p}, ms);
+        }
+    };
+    
+    elproto.animate = function (params, ms, easing, callback) {
+        var element = this;
+        if (element.removed) {
+            callback && callback.call(element);
+            return element;
+        }
+        var anim = params instanceof Animation ? params : R.animation(params, ms, easing, callback);
+        runAnimation(anim, element, anim.percents[0], null, element.attr());
+        return element;
+    };
+    
+    elproto.setTime = function (anim, value) {
+        if (anim && value != null) {
+            this.status(anim, mmin(value, anim.ms) / anim.ms);
+        }
+        return this;
+    };
+    
+    elproto.status = function (anim, value) {
+        var out = [],
+            i = 0,
+            len,
+            e;
+        if (value != null) {
+            runAnimation(anim, this, -1, mmin(value, 1));
+            return this;
+        } else {
+            len = animationElements.length;
+            for (; i < len; i++) {
+                e = animationElements[i];
+                if (e.el.id == this.id && (!anim || e.anim == anim)) {
+                    if (anim) {
+                        return e.status;
+                    }
+                    out.push({
+                        anim: e.anim,
+                        status: e.status
+                    });
+                }
+            }
+            if (anim) {
+                return 0;
+            }
+            return out;
+        }
+    };
+    
+    elproto.pause = function (anim) {
+        for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
+            if (eve("raphael.anim.pause." + this.id, this, animationElements[i].anim) !== false) {
+                animationElements[i].paused = true;
+            }
+        }
+        return this;
+    };
+    
+    elproto.resume = function (anim) {
+        for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
+            var e = animationElements[i];
+            if (eve("raphael.anim.resume." + this.id, this, e.anim) !== false) {
+                delete e.paused;
+                this.status(e.anim, e.status);
+            }
+        }
+        return this;
+    };
+    
+    elproto.stop = function (anim) {
+        for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
+            if (eve("raphael.anim.stop." + this.id, this, animationElements[i].anim) !== false) {
+                animationElements.splice(i--, 1);
+            }
+        }
+        return this;
+    };
+    function stopAnimation(paper) {
+        for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.paper == paper) {
+            animationElements.splice(i--, 1);
+        }
+    }
+    eve.on("raphael.remove", stopAnimation);
+    eve.on("raphael.clear", stopAnimation);
+    elproto.toString = function () {
+        return "Rapha\xebl\u2019s object";
+    };
+
+    // Set
+    var Set = function (items) {
+        this.items = [];
+        this.length = 0;
+        this.type = "set";
+        if (items) {
+            for (var i = 0, ii = items.length; i < ii; i++) {
+                if (items[i] && (items[i].constructor == elproto.constructor || items[i].constructor == Set)) {
+                    this[this.items.length] = this.items[this.items.length] = items[i];
+                    this.length++;
+                }
+            }
+        }
+    },
+    setproto = Set.prototype;
+    
+    setproto.push = function () {
+        var item,
+            len;
+        for (var i = 0, ii = arguments.length; i < ii; i++) {
+            item = arguments[i];
+            if (item && (item.constructor == elproto.constructor || item.constructor == Set)) {
+                len = this.items.length;
+                this[len] = this.items[len] = item;
+                this.length++;
+            }
+        }
+        return this;
+    };
+    
+    setproto.pop = function () {
+        this.length && delete this[this.length--];
+        return this.items.pop();
+    };
+    
+    setproto.forEach = function (callback, thisArg) {
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            if (callback.call(thisArg, this.items[i], i) === false) {
+                return this;
+            }
+        }
+        return this;
+    };
+    for (var method in elproto) if (elproto[has](method)) {
+        setproto[method] = (function (methodname) {
+            return function () {
+                var arg = arguments;
+                return this.forEach(function (el) {
+                    el[methodname][apply](el, arg);
+                });
+            };
+        })(method);
+    }
+    setproto.attr = function (name, value) {
+        if (name && R.is(name, array) && R.is(name[0], "object")) {
+            for (var j = 0, jj = name.length; j < jj; j++) {
+                this.items[j].attr(name[j]);
+            }
+        } else {
+            for (var i = 0, ii = this.items.length; i < ii; i++) {
+                this.items[i].attr(name, value);
+            }
+        }
+        return this;
+    };
+    
+    setproto.clear = function () {
+        while (this.length) {
+            this.pop();
+        }
+    };
+    
+    setproto.splice = function (index, count, insertion) {
+        index = index < 0 ? mmax(this.length + index, 0) : index;
+        count = mmax(0, mmin(this.length - index, count));
+        var tail = [],
+            todel = [],
+            args = [],
+            i;
+        for (i = 2; i < arguments.length; i++) {
+            args.push(arguments[i]);
+        }
+        for (i = 0; i < count; i++) {
+            todel.push(this[index + i]);
+        }
+        for (; i < this.length - index; i++) {
+            tail.push(this[index + i]);
+        }
+        var arglen = args.length;
+        for (i = 0; i < arglen + tail.length; i++) {
+            this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
+        }
+        i = this.items.length = this.length -= count - arglen;
+        while (this[i]) {
+            delete this[i++];
+        }
+        return new Set(todel);
+    };
+    
+    setproto.exclude = function (el) {
+        for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
+            this.splice(i, 1);
+            return true;
+        }
+    };
+    setproto.animate = function (params, ms, easing, callback) {
+        (R.is(easing, "function") || !easing) && (callback = easing || null);
+        var len = this.items.length,
+            i = len,
+            item,
+            set = this,
+            collector;
+        if (!len) {
+            return this;
+        }
+        callback && (collector = function () {
+            !--len && callback.call(set);
+        });
+        easing = R.is(easing, string) ? easing : collector;
+        var anim = R.animation(params, ms, easing, collector);
+        item = this.items[--i].animate(anim);
+        while (i--) {
+            this.items[i] && !this.items[i].removed && this.items[i].animateWith(item, anim, anim);
+        }
+        return this;
+    };
+    setproto.insertAfter = function (el) {
+        var i = this.items.length;
+        while (i--) {
+            this.items[i].insertAfter(el);
+        }
+        return this;
+    };
+    setproto.getBBox = function () {
+        var x = [],
+            y = [],
+            x2 = [],
+            y2 = [];
+        for (var i = this.items.length; i--;) if (!this.items[i].removed) {
+            var box = this.items[i].getBBox();
+            x.push(box.x);
+            y.push(box.y);
+            x2.push(box.x + box.width);
+            y2.push(box.y + box.height);
+        }
+        x = mmin[apply](0, x);
+        y = mmin[apply](0, y);
+        x2 = mmax[apply](0, x2);
+        y2 = mmax[apply](0, y2);
+        return {
+            x: x,
+            y: y,
+            x2: x2,
+            y2: y2,
+            width: x2 - x,
+            height: y2 - y
+        };
+    };
+    setproto.clone = function (s) {
+        s = new Set;
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            s.push(this.items[i].clone());
+        }
+        return s;
+    };
+    setproto.toString = function () {
+        return "Rapha\xebl\u2018s set";
+    };
+
+    
+    R.registerFont = function (font) {
+        if (!font.face) {
+            return font;
+        }
+        this.fonts = this.fonts || {};
+        var fontcopy = {
+                w: font.w,
+                face: {},
+                glyphs: {}
+            },
+            family = font.face["font-family"];
+        for (var prop in font.face) if (font.face[has](prop)) {
+            fontcopy.face[prop] = font.face[prop];
+        }
+        if (this.fonts[family]) {
+            this.fonts[family].push(fontcopy);
+        } else {
+            this.fonts[family] = [fontcopy];
+        }
+        if (!font.svg) {
+            fontcopy.face["units-per-em"] = toInt(font.face["units-per-em"], 10);
+            for (var glyph in font.glyphs) if (font.glyphs[has](glyph)) {
+                var path = font.glyphs[glyph];
+                fontcopy.glyphs[glyph] = {
+                    w: path.w,
+                    k: {},
+                    d: path.d && "M" + path.d.replace(/[mlcxtrv]/g, function (command) {
+                            return {l: "L", c: "C", x: "z", t: "m", r: "l", v: "c"}[command] || "M";
+                        }) + "z"
+                };
+                if (path.k) {
+                    for (var k in path.k) if (path[has](k)) {
+                        fontcopy.glyphs[glyph].k[k] = path.k[k];
+                    }
+                }
+            }
+        }
+        return font;
+    };
+    
+    paperproto.getFont = function (family, weight, style, stretch) {
+        stretch = stretch || "normal";
+        style = style || "normal";
+        weight = +weight || {normal: 400, bold: 700, lighter: 300, bolder: 800}[weight] || 400;
+        if (!R.fonts) {
+            return;
+        }
+        var font = R.fonts[family];
+        if (!font) {
+            var name = new RegExp("(^|\\s)" + family.replace(/[^\w\d\s+!~.:_-]/g, E) + "(\\s|$)", "i");
+            for (var fontName in R.fonts) if (R.fonts[has](fontName)) {
+                if (name.test(fontName)) {
+                    font = R.fonts[fontName];
+                    break;
+                }
+            }
+        }
+        var thefont;
+        if (font) {
+            for (var i = 0, ii = font.length; i < ii; i++) {
+                thefont = font[i];
+                if (thefont.face["font-weight"] == weight && (thefont.face["font-style"] == style || !thefont.face["font-style"]) && thefont.face["font-stretch"] == stretch) {
+                    break;
+                }
+            }
+        }
+        return thefont;
+    };
+    
+    paperproto.print = function (x, y, string, font, size, origin, letter_spacing) {
+        origin = origin || "middle"; // baseline|middle
+        letter_spacing = mmax(mmin(letter_spacing || 0, 1), -1);
+        var letters = Str(string)[split](E),
+            shift = 0,
+            notfirst = 0,
+            path = E,
+            scale;
+        R.is(font, string) && (font = this.getFont(font));
+        if (font) {
+            scale = (size || 16) / font.face["units-per-em"];
+            var bb = font.face.bbox[split](separator),
+                top = +bb[0],
+                lineHeight = bb[3] - bb[1],
+                shifty = 0,
+                height = +bb[1] + (origin == "baseline" ? lineHeight + (+font.face.descent) : lineHeight / 2);
+            for (var i = 0, ii = letters.length; i < ii; i++) {
+                if (letters[i] == "\n") {
+                    shift = 0;
+                    curr = 0;
+                    notfirst = 0;
+                    shifty += lineHeight;
+                } else {
+                    var prev = notfirst && font.glyphs[letters[i - 1]] || {},
+                        curr = font.glyphs[letters[i]];
+                    shift += notfirst ? (prev.w || font.w) + (prev.k && prev.k[letters[i]] || 0) + (font.w * letter_spacing) : 0;
+                    notfirst = 1;
+                }
+                if (curr && curr.d) {
+                    path += R.transformPath(curr.d, ["t", shift * scale, shifty * scale, "s", scale, scale, top, height, "t", (x - top) / scale, (y - height) / scale]);
+                }
+            }
+        }
+        return this.path(path).attr({
+            fill: "#000",
+            stroke: "none"
+        });
+    };
+
+    
+    paperproto.add = function (json) {
+        if (R.is(json, "array")) {
+            var res = this.set(),
+                i = 0,
+                ii = json.length,
+                j;
+            for (; i < ii; i++) {
+                j = json[i] || {};
+                elements[has](j.type) && res.push(this[j.type]().attr(j));
+            }
+        }
+        return res;
+    };
+
+    
+    R.format = function (token, params) {
+        var args = R.is(params, array) ? [0][concat](params) : arguments;
+        token && R.is(token, string) && args.length - 1 && (token = token.replace(formatrg, function (str, i) {
+            return args[++i] == null ? E : args[i];
+        }));
+        return token || E;
+    };
+    
+    R.fullfill = (function () {
+        var tokenRegex = /\{([^\}]+)\}/g,
+            objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
+            replacer = function (all, key, obj) {
+                var res = obj;
+                key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
+                    name = name || quotedName;
+                    if (res) {
+                        if (name in res) {
+                            res = res[name];
+                        }
+                        typeof res == "function" && isFunc && (res = res());
+                    }
+                });
+                res = (res == null || res == obj ? all : res) + "";
+                return res;
+            };
+        return function (str, obj) {
+            return String(str).replace(tokenRegex, function (all, key) {
+                return replacer(all, key, obj);
+            });
+        };
+    })();
+    
+    R.ninja = function () {
+        oldRaphael.was ? (g.win.Raphael = oldRaphael.is) : delete Raphael;
+        return R;
+    };
+    
+    R.st = setproto;
+    // Firefox <3.6 fix: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
+    (function (doc, loaded, f) {
+        if (doc.readyState == null && doc.addEventListener){
+            doc.addEventListener(loaded, f = function () {
+                doc.removeEventListener(loaded, f, false);
+                doc.readyState = "complete";
+            }, false);
+            doc.readyState = "loading";
+        }
+        function isLoaded() {
+            (/in/).test(doc.readyState) ? setTimeout(isLoaded, 9) : R.eve("raphael.DOMload");
+        }
+        isLoaded();
+    })(document, "DOMContentLoaded");
+
+    oldRaphael.was ? (g.win.Raphael = R) : (Raphael = R);
+    
+    eve.on("raphael.DOMload", function () {
+        loaded = true;
+    });
+})();
+
+
+// ┌─────────────────────────────────────────────────────────────────────┐ \\
+// │ Raphaël - JavaScript Vector Library                                 │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ SVG Module                                                          │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
+// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
+// └─────────────────────────────────────────────────────────────────────┘ \\
+window.Raphael.svg && function (R) {
+    var has = "hasOwnProperty",
+        Str = String,
+        toFloat = parseFloat,
+        toInt = parseInt,
+        math = Math,
+        mmax = math.max,
+        abs = math.abs,
+        pow = math.pow,
+        separator = /[, ]+/,
+        eve = R.eve,
+        E = "",
+        S = " ";
+    var xlink = "http://www.w3.org/1999/xlink",
+        markers = {
+            block: "M5,0 0,2.5 5,5z",
+            classic: "M5,0 0,2.5 5,5 3.5,3 3.5,2z",
+            diamond: "M2.5,0 5,2.5 2.5,5 0,2.5z",
+            open: "M6,1 1,3.5 6,6",
+            oval: "M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"
+        },
+        markerCounter = {};
+    R.toString = function () {
+        return  "Your browser supports SVG.\nYou are running Rapha\xebl " + this.version;
+    };
+    var $ = function (el, attr) {
+        if (attr) {
+            if (typeof el == "string") {
+                el = $(el);
+            }
+            for (var key in attr) if (attr[has](key)) {
+                if (key.substring(0, 6) == "xlink:") {
+                    el.setAttributeNS(xlink, key.substring(6), Str(attr[key]));
+                } else {
+                    el.setAttribute(key, Str(attr[key]));
+                }
+            }
+        } else {
+            el = R._g.doc.createElementNS("http://www.w3.org/2000/svg", el);
+            el.style && (el.style.webkitTapHighlightColor = "rgba(0,0,0,0)");
+        }
+        return el;
+    },
+    addGradientFill = function (element, gradient) {
+        var type = "linear",
+            id = element.id + gradient,
+            fx = .5, fy = .5,
+            o = element.node,
+            SVG = element.paper,
+            s = o.style,
+            el = R._g.doc.getElementById(id);
+        if (!el) {
+            gradient = Str(gradient).replace(R._radial_gradient, function (all, _fx, _fy) {
+                type = "radial";
+                if (_fx && _fy) {
+                    fx = toFloat(_fx);
+                    fy = toFloat(_fy);
+                    var dir = ((fy > .5) * 2 - 1);
+                    pow(fx - .5, 2) + pow(fy - .5, 2) > .25 &&
+                        (fy = math.sqrt(.25 - pow(fx - .5, 2)) * dir + .5) &&
+                        fy != .5 &&
+                        (fy = fy.toFixed(5) - 1e-5 * dir);
+                }
+                return E;
+            });
+            gradient = gradient.split(/\s*\-\s*/);
+            if (type == "linear") {
+                var angle = gradient.shift();
+                angle = -toFloat(angle);
+                if (isNaN(angle)) {
+                    return null;
+                }
+                var vector = [0, 0, math.cos(R.rad(angle)), math.sin(R.rad(angle))],
+                    max = 1 / (mmax(abs(vector[2]), abs(vector[3])) || 1);
+                vector[2] *= max;
+                vector[3] *= max;
+                if (vector[2] < 0) {
+                    vector[0] = -vector[2];
+                    vector[2] = 0;
+                }
+                if (vector[3] < 0) {
+                    vector[1] = -vector[3];
+                    vector[3] = 0;
+                }
+            }
+            var dots = R._parseDots(gradient);
+            if (!dots) {
+                return null;
+            }
+            id = id.replace(/[\(\)\s,\xb0#]/g, "_");
+            
+            if (element.gradient && id != element.gradient.id) {
+                SVG.defs.removeChild(element.gradient);
+                delete element.gradient;
+            }
+
+            if (!element.gradient) {
+                el = $(type + "Gradient", {id: id});
+                element.gradient = el;
+                $(el, type == "radial" ? {
+                    fx: fx,
+                    fy: fy
+                } : {
+                    x1: vector[0],
+                    y1: vector[1],
+                    x2: vector[2],
+                    y2: vector[3],
+                    gradientTransform: element.matrix.invert()
+                });
+                SVG.defs.appendChild(el);
+                for (var i = 0, ii = dots.length; i < ii; i++) {
+                    el.appendChild($("stop", {
+                        offset: dots[i].offset ? dots[i].offset : i ? "100%" : "0%",
+                        "stop-color": dots[i].color || "#fff"
+                    }));
+                }
+            }
+        }
+        $(o, {
+            fill: "url(#" + id + ")",
+            opacity: 1,
+            "fill-opacity": 1
+        });
+        s.fill = E;
+        s.opacity = 1;
+        s.fillOpacity = 1;
+        return 1;
+    },
+    updatePosition = function (o) {
+        var bbox = o.getBBox(1);
+        $(o.pattern, {patternTransform: o.matrix.invert() + " translate(" + bbox.x + "," + bbox.y + ")"});
+    },
+    addArrow = function (o, value, isEnd) {
+        if (o.type == "path") {
+            var values = Str(value).toLowerCase().split("-"),
+                p = o.paper,
+                se = isEnd ? "end" : "start",
+                node = o.node,
+                attrs = o.attrs,
+                stroke = attrs["stroke-width"],
+                i = values.length,
+                type = "classic",
+                from,
+                to,
+                dx,
+                refX,
+                attr,
+                w = 3,
+                h = 3,
+                t = 5;
+            while (i--) {
+                switch (values[i]) {
+                    case "block":
+                    case "classic":
+                    case "oval":
+                    case "diamond":
+                    case "open":
+                    case "none":
+                        type = values[i];
+                        break;
+                    case "wide": h = 5; break;
+                    case "narrow": h = 2; break;
+                    case "long": w = 5; break;
+                    case "short": w = 2; break;
+                }
+            }
+            if (type == "open") {
+                w += 2;
+                h += 2;
+                t += 2;
+                dx = 1;
+                refX = isEnd ? 4 : 1;
+                attr = {
+                    fill: "none",
+                    stroke: attrs.stroke
+                };
+            } else {
+                refX = dx = w / 2;
+                attr = {
+                    fill: attrs.stroke,
+                    stroke: "none"
+                };
+            }
+            if (o._.arrows) {
+                if (isEnd) {
+                    o._.arrows.endPath && markerCounter[o._.arrows.endPath]--;
+                    o._.arrows.endMarker && markerCounter[o._.arrows.endMarker]--;
+                } else {
+                    o._.arrows.startPath && markerCounter[o._.arrows.startPath]--;
+                    o._.arrows.startMarker && markerCounter[o._.arrows.startMarker]--;
+                }
+            } else {
+                o._.arrows = {};
+            }
+            if (type != "none") {
+                var pathId = "raphael-marker-" + type,
+                    markerId = "raphael-marker-" + se + type + w + h;
+                if (!R._g.doc.getElementById(pathId)) {
+                    p.defs.appendChild($($("path"), {
+                        "stroke-linecap": "round",
+                        d: markers[type],
+                        id: pathId
+                    }));
+                    markerCounter[pathId] = 1;
+                } else {
+                    markerCounter[pathId]++;
+                }
+                var marker = R._g.doc.getElementById(markerId),
+                    use;
+                if (!marker) {
+                    marker = $($("marker"), {
+                        id: markerId,
+                        markerHeight: h,
+                        markerWidth: w,
+                        orient: "auto",
+                        refX: refX,
+                        refY: h / 2
+                    });
+                    use = $($("use"), {
+                        "xlink:href": "#" + pathId,
+                        transform: (isEnd ? "rotate(180 " + w / 2 + " " + h / 2 + ") " : E) + "scale(" + w / t + "," + h / t + ")",
+                        "stroke-width": (1 / ((w / t + h / t) / 2)).toFixed(4)
+                    });
+                    marker.appendChild(use);
+                    p.defs.appendChild(marker);
+                    markerCounter[markerId] = 1;
+                } else {
+                    markerCounter[markerId]++;
+                    use = marker.getElementsByTagName("use")[0];
+                }
+                $(use, attr);
+                var delta = dx * (type != "diamond" && type != "oval");
+                if (isEnd) {
+                    from = o._.arrows.startdx * stroke || 0;
+                    to = R.getTotalLength(attrs.path) - delta * stroke;
+                } else {
+                    from = delta * stroke;
+                    to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
+                }
+                attr = {};
+                attr["marker-" + se] = "url(#" + markerId + ")";
+                if (to || from) {
+                    attr.d = Raphael.getSubpath(attrs.path, from, to);
+                }
+                $(node, attr);
+                o._.arrows[se + "Path"] = pathId;
+                o._.arrows[se + "Marker"] = markerId;
+                o._.arrows[se + "dx"] = delta;
+                o._.arrows[se + "Type"] = type;
+                o._.arrows[se + "String"] = value;
+            } else {
+                if (isEnd) {
+                    from = o._.arrows.startdx * stroke || 0;
+                    to = R.getTotalLength(attrs.path) - from;
+                } else {
+                    from = 0;
+                    to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
+                }
+                o._.arrows[se + "Path"] && $(node, {d: Raphael.getSubpath(attrs.path, from, to)});
+                delete o._.arrows[se + "Path"];
+                delete o._.arrows[se + "Marker"];
+                delete o._.arrows[se + "dx"];
+                delete o._.arrows[se + "Type"];
+                delete o._.arrows[se + "String"];
+            }
+            for (attr in markerCounter) if (markerCounter[has](attr) && !markerCounter[attr]) {
+                var item = R._g.doc.getElementById(attr);
+                item && item.parentNode.removeChild(item);
+            }
+        }
+    },
+    dasharray = {
+        "": [0],
+        "none": [0],
+        "-": [3, 1],
+        ".": [1, 1],
+        "-.": [3, 1, 1, 1],
+        "-..": [3, 1, 1, 1, 1, 1],
+        ". ": [1, 3],
+        "- ": [4, 3],
+        "--": [8, 3],
+        "- .": [4, 3, 1, 3],
+        "--.": [8, 3, 1, 3],
+        "--..": [8, 3, 1, 3, 1, 3]
+    },
+    addDashes = function (o, value, params) {
+        value = dasharray[Str(value).toLowerCase()];
+        if (value) {
+            var width = o.attrs["stroke-width"] || "1",
+                butt = {round: width, square: width, butt: 0}[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0,
+                dashes = [],
+                i = value.length;
+            while (i--) {
+                dashes[i] = value[i] * width + ((i % 2) ? 1 : -1) * butt;
+            }
+            $(o.node, {"stroke-dasharray": dashes.join(",")});
+        }
+    },
+    setFillAndStroke = function (o, params) {
+        var node = o.node,
+            attrs = o.attrs,
+            vis = node.style.visibility;
+        node.style.visibility = "hidden";
+        for (var att in params) {
+            if (params[has](att)) {
+                if (!R._availableAttrs[has](att)) {
+                    continue;
+                }
+                var value = params[att];
+                attrs[att] = value;
+                switch (att) {
+                    case "blur":
+                        o.blur(value);
+                        break;
+                    case "href":
+                    case "title":
+                    case "target":
+                        var pn = node.parentNode;
+                        if (pn.tagName.toLowerCase() != "a") {
+                            var hl = $("a");
+                            pn.insertBefore(hl, node);
+                            hl.appendChild(node);
+                            pn = hl;
+                        }
+                        if (att == "target") {
+                            pn.setAttributeNS(xlink, "show", value == "blank" ? "new" : value);
+                        } else {
+                            pn.setAttributeNS(xlink, att, value);
+                        }
+                        break;
+                    case "cursor":
+                        node.style.cursor = value;
+                        break;
+                    case "transform":
+                        o.transform(value);
+                        break;
+                    case "arrow-start":
+                        addArrow(o, value);
+                        break;
+                    case "arrow-end":
+                        addArrow(o, value, 1);
+                        break;
+                    case "clip-rect":
+                        var rect = Str(value).split(separator);
+                        if (rect.length == 4) {
+                            o.clip && o.clip.parentNode.parentNode.removeChild(o.clip.parentNode);
+                            var el = $("clipPath"),
+                                rc = $("rect");
+                            el.id = R.createUUID();
+                            $(rc, {
+                                x: rect[0],
+                                y: rect[1],
+                                width: rect[2],
+                                height: rect[3]
+                            });
+                            el.appendChild(rc);
+                            o.paper.defs.appendChild(el);
+                            $(node, {"clip-path": "url(#" + el.id + ")"});
+                            o.clip = rc;
+                        }
+                        if (!value) {
+                            var path = node.getAttribute("clip-path");
+                            if (path) {
+                                var clip = R._g.doc.getElementById(path.replace(/(^url\(#|\)$)/g, E));
+                                clip && clip.parentNode.removeChild(clip);
+                                $(node, {"clip-path": E});
+                                delete o.clip;
+                            }
+                        }
+                    break;
+                    case "path":
+                        if (o.type == "path") {
+                            $(node, {d: value ? attrs.path = R._pathToAbsolute(value) : "M0,0"});
+                            o._.dirty = 1;
+                            if (o._.arrows) {
+                                "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
+                                "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
+                            }
+                        }
+                        break;
+                    case "width":
+                        node.setAttribute(att, value);
+                        o._.dirty = 1;
+                        if (attrs.fx) {
+                            att = "x";
+                            value = attrs.x;
+                        } else {
+                            break;
+                        }
+                    case "x":
+                        if (attrs.fx) {
+                            value = -attrs.x - (attrs.width || 0);
+                        }
+                    case "rx":
+                        if (att == "rx" && o.type == "rect") {
+                            break;
+                        }
+                    case "cx":
+                        node.setAttribute(att, value);
+                        o.pattern && updatePosition(o);
+                        o._.dirty = 1;
+                        break;
+                    case "height":
+                        node.setAttribute(att, value);
+                        o._.dirty = 1;
+                        if (attrs.fy) {
+                            att = "y";
+                            value = attrs.y;
+                        } else {
+                            break;
+                        }
+                    case "y":
+                        if (attrs.fy) {
+                            value = -attrs.y - (attrs.height || 0);
+                        }
+                    case "ry":
+                        if (att == "ry" && o.type == "rect") {
+                            break;
+                        }
+                    case "cy":
+                        node.setAttribute(att, value);
+                        o.pattern && updatePosition(o);
+                        o._.dirty = 1;
+                        break;
+                    case "r":
+                        if (o.type == "rect") {
+                            $(node, {rx: value, ry: value});
+                        } else {
+                            node.setAttribute(att, value);
+                        }
+                        o._.dirty = 1;
+                        break;
+                    case "src":
+                        if (o.type == "image") {
+                            node.setAttributeNS(xlink, "href", value);
+                        }
+                        break;
+                    case "stroke-width":
+                        if (o._.sx != 1 || o._.sy != 1) {
+                            value /= mmax(abs(o._.sx), abs(o._.sy)) || 1;
+                        }
+                        if (o.paper._vbSize) {
+                            value *= o.paper._vbSize;
+                        }
+                        node.setAttribute(att, value);
+                        if (attrs["stroke-dasharray"]) {
+                            addDashes(o, attrs["stroke-dasharray"], params);
+                        }
+                        if (o._.arrows) {
+                            "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
+                            "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
+                        }
+                        break;
+                    case "stroke-dasharray":
+                        addDashes(o, value, params);
+                        break;
+                    case "fill":
+                        var isURL = Str(value).match(R._ISURL);
+                        if (isURL) {
+                            el = $("pattern");
+                            var ig = $("image");
+                            el.id = R.createUUID();
+                            $(el, {x: 0, y: 0, patternUnits: "userSpaceOnUse", height: 1, width: 1});
+                            $(ig, {x: 0, y: 0, "xlink:href": isURL[1]});
+                            el.appendChild(ig);
+
+                            (function (el) {
+                                R._preload(isURL[1], function () {
+                                    var w = this.offsetWidth,
+                                        h = this.offsetHeight;
+                                    $(el, {width: w, height: h});
+                                    $(ig, {width: w, height: h});
+                                    o.paper.safari();
+                                });
+                            })(el);
+                            o.paper.defs.appendChild(el);
+                            $(node, {fill: "url(#" + el.id + ")"});
+                            o.pattern = el;
+                            o.pattern && updatePosition(o);
+                            break;
+                        }
+                        var clr = R.getRGB(value);
+                        if (!clr.error) {
+                            delete params.gradient;
+                            delete attrs.gradient;
+                            !R.is(attrs.opacity, "undefined") &&
+                                R.is(params.opacity, "undefined") &&
+                                $(node, {opacity: attrs.opacity});
+                            !R.is(attrs["fill-opacity"], "undefined") &&
+                                R.is(params["fill-opacity"], "undefined") &&
+                                $(node, {"fill-opacity": attrs["fill-opacity"]});
+                        } else if ((o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value)) {
+                            if ("opacity" in attrs || "fill-opacity" in attrs) {
+                                var gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
+                                if (gradient) {
+                                    var stops = gradient.getElementsByTagName("stop");
+                                    $(stops[stops.length - 1], {"stop-opacity": ("opacity" in attrs ? attrs.opacity : 1) * ("fill-opacity" in attrs ? attrs["fill-opacity"] : 1)});
+                                }
+                            }
+                            attrs.gradient = value;
+                            attrs.fill = "none";
+                            break;
+                        }
+                        clr[has]("opacity") && $(node, {"fill-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
+                    case "stroke":
+                        clr = R.getRGB(value);
+                        node.setAttribute(att, clr.hex);
+                        att == "stroke" && clr[has]("opacity") && $(node, {"stroke-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
+                        if (att == "stroke" && o._.arrows) {
+                            "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
+                            "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
+                        }
+                        break;
+                    case "gradient":
+                        (o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value);
+                        break;
+                    case "opacity":
+                        if (attrs.gradient && !attrs[has]("stroke-opacity")) {
+                            $(node, {"stroke-opacity": value > 1 ? value / 100 : value});
+                        }
+                        // fall
+                    case "fill-opacity":
+                        if (attrs.gradient) {
+                            gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
+                            if (gradient) {
+                                stops = gradient.getElementsByTagName("stop");
+                                $(stops[stops.length - 1], {"stop-opacity": value});
+                            }
+                            break;
+                        }
+                    default:
+                        att == "font-size" && (value = toInt(value, 10) + "px");
+                        var cssrule = att.replace(/(\-.)/g, function (w) {
+                            return w.substring(1).toUpperCase();
+                        });
+                        node.style[cssrule] = value;
+                        o._.dirty = 1;
+                        node.setAttribute(att, value);
+                        break;
+                }
+            }
+        }
+
+        tuneText(o, params);
+        node.style.visibility = vis;
+    },
+    leading = 1.2,
+    tuneText = function (el, params) {
+        if (el.type != "text" || !(params[has]("text") || params[has]("font") || params[has]("font-size") || params[has]("x") || params[has]("y"))) {
+            return;
+        }
+        var a = el.attrs,
+            node = el.node,
+            fontSize = node.firstChild ? toInt(R._g.doc.defaultView.getComputedStyle(node.firstChild, E).getPropertyValue("font-size"), 10) : 10;
+
+        if (params[has]("text")) {
+            a.text = params.text;
+            while (node.firstChild) {
+                node.removeChild(node.firstChild);
+            }
+            var texts = Str(params.text).split("\n"),
+                tspans = [],
+                tspan;
+            for (var i = 0, ii = texts.length; i < ii; i++) {
+                tspan = $("tspan");
+                i && $(tspan, {dy: fontSize * leading, x: a.x});
+                tspan.appendChild(R._g.doc.createTextNode(texts[i]));
+                node.appendChild(tspan);
+                tspans[i] = tspan;
+            }
+        } else {
+            tspans = node.getElementsByTagName("tspan");
+            for (i = 0, ii = tspans.length; i < ii; i++) if (i) {
+                $(tspans[i], {dy: fontSize * leading, x: a.x});
+            } else {
+                $(tspans[0], {dy: 0});
+            }
+        }
+        $(node, {x: a.x, y: a.y});
+        el._.dirty = 1;
+        var bb = el._getBBox(),
+            dif = a.y - (bb.y + bb.height / 2);
+        dif && R.is(dif, "finite") && $(tspans[0], {dy: dif});
+    },
+    Element = function (node, svg) {
+        var X = 0,
+            Y = 0;
+        
+        this[0] = this.node = node;
+        
+        node.raphael = true;
+        
+        this.id = R._oid++;
+        node.raphaelid = this.id;
+        this.matrix = R.matrix();
+        this.realPath = null;
+        
+        this.paper = svg;
+        this.attrs = this.attrs || {};
+        this._ = {
+            transform: [],
+            sx: 1,
+            sy: 1,
+            deg: 0,
+            dx: 0,
+            dy: 0,
+            dirty: 1
+        };
+        !svg.bottom && (svg.bottom = this);
+        
+        this.prev = svg.top;
+        svg.top && (svg.top.next = this);
+        svg.top = this;
+        
+        this.next = null;
+    },
+    elproto = R.el;
+
+    Element.prototype = elproto;
+    elproto.constructor = Element;
+
+    R._engine.path = function (pathString, SVG) {
+        var el = $("path");
+        SVG.canvas && SVG.canvas.appendChild(el);
+        var p = new Element(el, SVG);
+        p.type = "path";
+        setFillAndStroke(p, {
+            fill: "none",
+            stroke: "#000",
+            path: pathString
+        });
+        return p;
+    };
+    
+    elproto.rotate = function (deg, cx, cy) {
+        if (this.removed) {
+            return this;
+        }
+        deg = Str(deg).split(separator);
+        if (deg.length - 1) {
+            cx = toFloat(deg[1]);
+            cy = toFloat(deg[2]);
+        }
+        deg = toFloat(deg[0]);
+        (cy == null) && (cx = cy);
+        if (cx == null || cy == null) {
+            var bbox = this.getBBox(1);
+            cx = bbox.x + bbox.width / 2;
+            cy = bbox.y + bbox.height / 2;
+        }
+        this.transform(this._.transform.concat([["r", deg, cx, cy]]));
+        return this;
+    };
+    
+    elproto.scale = function (sx, sy, cx, cy) {
+        if (this.removed) {
+            return this;
+        }
+        sx = Str(sx).split(separator);
+        if (sx.length - 1) {
+            sy = toFloat(sx[1]);
+            cx = toFloat(sx[2]);
+            cy = toFloat(sx[3]);
+        }
+        sx = toFloat(sx[0]);
+        (sy == null) && (sy = sx);
+        (cy == null) && (cx = cy);
+        if (cx == null || cy == null) {
+            var bbox = this.getBBox(1);
+        }
+        cx = cx == null ? bbox.x + bbox.width / 2 : cx;
+        cy = cy == null ? bbox.y + bbox.height / 2 : cy;
+        this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
+        return this;
+    };
+    
+    elproto.translate = function (dx, dy) {
+        if (this.removed) {
+            return this;
+        }
+        dx = Str(dx).split(separator);
+        if (dx.length - 1) {
+            dy = toFloat(dx[1]);
+        }
+        dx = toFloat(dx[0]) || 0;
+        dy = +dy || 0;
+        this.transform(this._.transform.concat([["t", dx, dy]]));
+        return this;
+    };
+    
+    elproto.transform = function (tstr) {
+        var _ = this._;
+        if (tstr == null) {
+            return _.transform;
+        }
+        R._extractTransform(this, tstr);
+
+        this.clip && $(this.clip, {transform: this.matrix.invert()});
+        this.pattern && updatePosition(this);
+        this.node && $(this.node, {transform: this.matrix});
+    
+        if (_.sx != 1 || _.sy != 1) {
+            var sw = this.attrs[has]("stroke-width") ? this.attrs["stroke-width"] : 1;
+            this.attr({"stroke-width": sw});
+        }
+
+        return this;
+    };
+    
+    elproto.hide = function () {
+        !this.removed && this.paper.safari(this.node.style.display = "none");
+        return this;
+    };
+    
+    elproto.show = function () {
+        !this.removed && this.paper.safari(this.node.style.display = "");
+        return this;
+    };
+    
+    elproto.remove = function () {
+        if (this.removed || !this.node.parentNode) {
+            return;
+        }
+        var paper = this.paper;
+        paper.__set__ && paper.__set__.exclude(this);
+        eve.unbind("raphael.*.*." + this.id);
+        if (this.gradient) {
+            paper.defs.removeChild(this.gradient);
+        }
+        R._tear(this, paper);
+        if (this.node.parentNode.tagName.toLowerCase() == "a") {
+            this.node.parentNode.parentNode.removeChild(this.node.parentNode);
+        } else {
+            this.node.parentNode.removeChild(this.node);
+        }
+        for (var i in this) {
+            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
+        }
+        this.removed = true;
+    };
+    elproto._getBBox = function () {
+        if (this.node.style.display == "none") {
+            this.show();
+            var hide = true;
+        }
+        var bbox = {};
+        try {
+            bbox = this.node.getBBox();
+        } catch(e) {
+            // Firefox 3.0.x plays badly here
+        } finally {
+            bbox = bbox || {};
+        }
+        hide && this.hide();
+        return bbox;
+    };
+    
+    elproto.attr = function (name, value) {
+        if (this.removed) {
+            return this;
+        }
+        if (name == null) {
+            var res = {};
+            for (var a in this.attrs) if (this.attrs[has](a)) {
+                res[a] = this.attrs[a];
+            }
+            res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
+            res.transform = this._.transform;
+            return res;
+        }
+        if (value == null && R.is(name, "string")) {
+            if (name == "fill" && this.attrs.fill == "none" && this.attrs.gradient) {
+                return this.attrs.gradient;
+            }
+            if (name == "transform") {
+                return this._.transform;
+            }
+            var names = name.split(separator),
+                out = {};
+            for (var i = 0, ii = names.length; i < ii; i++) {
+                name = names[i];
+                if (name in this.attrs) {
+                    out[name] = this.attrs[name];
+                } else if (R.is(this.paper.customAttributes[name], "function")) {
+                    out[name] = this.paper.customAttributes[name].def;
+                } else {
+                    out[name] = R._availableAttrs[name];
+                }
+            }
+            return ii - 1 ? out : out[names[0]];
+        }
+        if (value == null && R.is(name, "array")) {
+            out = {};
+            for (i = 0, ii = name.length; i < ii; i++) {
+                out[name[i]] = this.attr(name[i]);
+            }
+            return out;
+        }
+        if (value != null) {
+            var params = {};
+            params[name] = value;
+        } else if (name != null && R.is(name, "object")) {
+            params = name;
+        }
+        for (var key in params) {
+            eve("raphael.attr." + key + "." + this.id, this, params[key]);
+        }
+        for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
+            var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
+            this.attrs[key] = params[key];
+            for (var subkey in par) if (par[has](subkey)) {
+                params[subkey] = par[subkey];
+            }
+        }
+        setFillAndStroke(this, params);
+        return this;
+    };
+    
+    elproto.toFront = function () {
+        if (this.removed) {
+            return this;
+        }
+        if (this.node.parentNode.tagName.toLowerCase() == "a") {
+            this.node.parentNode.parentNode.appendChild(this.node.parentNode);
+        } else {
+            this.node.parentNode.appendChild(this.node);
+        }
+        var svg = this.paper;
+        svg.top != this && R._tofront(this, svg);
+        return this;
+    };
+    
+    elproto.toBack = function () {
+        if (this.removed) {
+            return this;
+        }
+        var parent = this.node.parentNode;
+        if (parent.tagName.toLowerCase() == "a") {
+            parent.parentNode.insertBefore(this.node.parentNode, this.node.parentNode.parentNode.firstChild); 
+        } else if (parent.firstChild != this.node) {
+            parent.insertBefore(this.node, this.node.parentNode.firstChild);
+        }
+        R._toback(this, this.paper);
+        var svg = this.paper;
+        return this;
+    };
+    
+    elproto.insertAfter = function (element) {
+        if (this.removed) {
+            return this;
+        }
+        var node = element.node || element[element.length - 1].node;
+        if (node.nextSibling) {
+            node.parentNode.insertBefore(this.node, node.nextSibling);
+        } else {
+            node.parentNode.appendChild(this.node);
+        }
+        R._insertafter(this, element, this.paper);
+        return this;
+    };
+    
+    elproto.insertBefore = function (element) {
+        if (this.removed) {
+            return this;
+        }
+        var node = element.node || element[0].node;
+        node.parentNode.insertBefore(this.node, node);
+        R._insertbefore(this, element, this.paper);
+        return this;
+    };
+    elproto.blur = function (size) {
+        // Experimental. No Safari support. Use it on your own risk.
+        var t = this;
+        if (+size !== 0) {
+            var fltr = $("filter"),
+                blur = $("feGaussianBlur");
+            t.attrs.blur = size;
+            fltr.id = R.createUUID();
+            $(blur, {stdDeviation: +size || 1.5});
+            fltr.appendChild(blur);
+            t.paper.defs.appendChild(fltr);
+            t._blur = fltr;
+            $(t.node, {filter: "url(#" + fltr.id + ")"});
+        } else {
+            if (t._blur) {
+                t._blur.parentNode.removeChild(t._blur);
+                delete t._blur;
+                delete t.attrs.blur;
+            }
+            t.node.removeAttribute("filter");
+        }
+    };
+    R._engine.circle = function (svg, x, y, r) {
+        var el = $("circle");
+        svg.canvas && svg.canvas.appendChild(el);
+        var res = new Element(el, svg);
+        res.attrs = {cx: x, cy: y, r: r, fill: "none", stroke: "#000"};
+        res.type = "circle";
+        $(el, res.attrs);
+        return res;
+    };
+    R._engine.rect = function (svg, x, y, w, h, r) {
+        var el = $("rect");
+        svg.canvas && svg.canvas.appendChild(el);
+        var res = new Element(el, svg);
+        res.attrs = {x: x, y: y, width: w, height: h, r: r || 0, rx: r || 0, ry: r || 0, fill: "none", stroke: "#000"};
+        res.type = "rect";
+        $(el, res.attrs);
+        return res;
+    };
+    R._engine.ellipse = function (svg, x, y, rx, ry) {
+        var el = $("ellipse");
+        svg.canvas && svg.canvas.appendChild(el);
+        var res = new Element(el, svg);
+        res.attrs = {cx: x, cy: y, rx: rx, ry: ry, fill: "none", stroke: "#000"};
+        res.type = "ellipse";
+        $(el, res.attrs);
+        return res;
+    };
+    R._engine.image = function (svg, src, x, y, w, h) {
+        var el = $("image");
+        $(el, {x: x, y: y, width: w, height: h, preserveAspectRatio: "none"});
+        el.setAttributeNS(xlink, "href", src);
+        svg.canvas && svg.canvas.appendChild(el);
+        var res = new Element(el, svg);
+        res.attrs = {x: x, y: y, width: w, height: h, src: src};
+        res.type = "image";
+        return res;
+    };
+    R._engine.text = function (svg, x, y, text) {
+        var el = $("text");
+        svg.canvas && svg.canvas.appendChild(el);
+        var res = new Element(el, svg);
+        res.attrs = {
+            x: x,
+            y: y,
+            "text-anchor": "middle",
+            text: text,
+            font: R._availableAttrs.font,
+            stroke: "none",
+            fill: "#000"
+        };
+        res.type = "text";
+        setFillAndStroke(res, res.attrs);
+        return res;
+    };
+    R._engine.setSize = function (width, height) {
+        this.width = width || this.width;
+        this.height = height || this.height;
+        this.canvas.setAttribute("width", this.width);
+        this.canvas.setAttribute("height", this.height);
+        if (this._viewBox) {
+            this.setViewBox.apply(this, this._viewBox);
+        }
+        return this;
+    };
+    R._engine.create = function () {
+        var con = R._getContainer.apply(0, arguments),
+            container = con && con.container,
+            x = con.x,
+            y = con.y,
+            width = con.width,
+            height = con.height;
+        if (!container) {
+            throw new Error("SVG container not found.");
+        }
+        var cnvs = $("svg"),
+            css = "overflow:hidden;",
+            isFloating;
+        x = x || 0;
+        y = y || 0;
+        width = width || 512;
+        height = height || 342;
+        $(cnvs, {
+            height: height,
+            version: 1.1,
+            width: width,
+            xmlns: "http://www.w3.org/2000/svg"
+        });
+        if (container == 1) {
+            cnvs.style.cssText = css + "position:absolute;left:" + x + "px;top:" + y + "px";
+            R._g.doc.body.appendChild(cnvs);
+            isFloating = 1;
+        } else {
+            cnvs.style.cssText = css + "position:relative";
+            if (container.firstChild) {
+                container.insertBefore(cnvs, container.firstChild);
+            } else {
+                container.appendChild(cnvs);
+            }
+        }
+        container = new R._Paper;
+        container.width = width;
+        container.height = height;
+        container.canvas = cnvs;
+        container.clear();
+        container._left = container._top = 0;
+        isFloating && (container.renderfix = function () {});
+        container.renderfix();
+        return container;
+    };
+    R._engine.setViewBox = function (x, y, w, h, fit) {
+        eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
+        var size = mmax(w / this.width, h / this.height),
+            top = this.top,
+            aspectRatio = fit ? "meet" : "xMinYMin",
+            vb,
+            sw;
+        if (x == null) {
+            if (this._vbSize) {
+                size = 1;
+            }
+            delete this._vbSize;
+            vb = "0 0 " + this.width + S + this.height;
+        } else {
+            this._vbSize = size;
+            vb = x + S + y + S + w + S + h;
+        }
+        $(this.canvas, {
+            viewBox: vb,
+            preserveAspectRatio: aspectRatio
+        });
+        while (size && top) {
+            sw = "stroke-width" in top.attrs ? top.attrs["stroke-width"] : 1;
+            top.attr({"stroke-width": sw});
+            top._.dirty = 1;
+            top._.dirtyT = 1;
+            top = top.prev;
+        }
+        this._viewBox = [x, y, w, h, !!fit];
+        return this;
+    };
+    
+    R.prototype.renderfix = function () {
+        var cnvs = this.canvas,
+            s = cnvs.style,
+            pos;
+        try {
+            pos = cnvs.getScreenCTM() || cnvs.createSVGMatrix();
+        } catch (e) {
+            pos = cnvs.createSVGMatrix();
+        }
+        var left = -pos.e % 1,
+            top = -pos.f % 1;
+        if (left || top) {
+            if (left) {
+                this._left = (this._left + left) % 1;
+                s.left = this._left + "px";
+            }
+            if (top) {
+                this._top = (this._top + top) % 1;
+                s.top = this._top + "px";
+            }
+        }
+    };
+    
+    R.prototype.clear = function () {
+        R.eve("raphael.clear", this);
+        var c = this.canvas;
+        while (c.firstChild) {
+            c.removeChild(c.firstChild);
+        }
+        this.bottom = this.top = null;
+        (this.desc = $("desc")).appendChild(R._g.doc.createTextNode("Created with Rapha\xebl " + R.version));
+        c.appendChild(this.desc);
+        c.appendChild(this.defs = $("defs"));
+    };
+    
+    R.prototype.remove = function () {
+        eve("raphael.remove", this);
+        this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
+        for (var i in this) {
+            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
+        }
+    };
+    var setproto = R.st;
+    for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
+        setproto[method] = (function (methodname) {
+            return function () {
+                var arg = arguments;
+                return this.forEach(function (el) {
+                    el[methodname].apply(el, arg);
+                });
+            };
+        })(method);
+    }
+}(window.Raphael);
+
+// ┌─────────────────────────────────────────────────────────────────────┐ \\
+// │ Raphaël - JavaScript Vector Library                                 │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ VML Module                                                          │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
+// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
+// └─────────────────────────────────────────────────────────────────────┘ \\
+window.Raphael.vml && function (R) {
+    var has = "hasOwnProperty",
+        Str = String,
+        toFloat = parseFloat,
+        math = Math,
+        round = math.round,
+        mmax = math.max,
+        mmin = math.min,
+        abs = math.abs,
+        fillString = "fill",
+        separator = /[, ]+/,
+        eve = R.eve,
+        ms = " progid:DXImageTransform.Microsoft",
+        S = " ",
+        E = "",
+        map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
+        bites = /([clmz]),?([^clmz]*)/gi,
+        blurregexp = / progid:\S+Blur\([^\)]+\)/g,
+        val = /-?[^,\s-]+/g,
+        cssDot = "position:absolute;left:0;top:0;width:1px;height:1px",
+        zoom = 21600,
+        pathTypes = {path: 1, rect: 1, image: 1},
+        ovalTypes = {circle: 1, ellipse: 1},
+        path2vml = function (path) {
+            var total =  /[ahqstv]/ig,
+                command = R._pathToAbsolute;
+            Str(path).match(total) && (command = R._path2curve);
+            total = /[clmz]/g;
+            if (command == R._pathToAbsolute && !Str(path).match(total)) {
+                var res = Str(path).replace(bites, function (all, command, args) {
+                    var vals = [],
+                        isMove = command.toLowerCase() == "m",
+                        res = map[command];
+                    args.replace(val, function (value) {
+                        if (isMove && vals.length == 2) {
+                            res += vals + map[command == "m" ? "l" : "L"];
+                            vals = [];
+                        }
+                        vals.push(round(value * zoom));
+                    });
+                    return res + vals;
+                });
+                return res;
+            }
+            var pa = command(path), p, r;
+            res = [];
+            for (var i = 0, ii = pa.length; i < ii; i++) {
+                p = pa[i];
+                r = pa[i][0].toLowerCase();
+                r == "z" && (r = "x");
+                for (var j = 1, jj = p.length; j < jj; j++) {
+                    r += round(p[j] * zoom) + (j != jj - 1 ? "," : E);
+                }
+                res.push(r);
+            }
+            return res.join(S);
+        },
+        compensation = function (deg, dx, dy) {
+            var m = R.matrix();
+            m.rotate(-deg, .5, .5);
+            return {
+                dx: m.x(dx, dy),
+                dy: m.y(dx, dy)
+            };
+        },
+        setCoords = function (p, sx, sy, dx, dy, deg) {
+            var _ = p._,
+                m = p.matrix,
+                fillpos = _.fillpos,
+                o = p.node,
+                s = o.style,
+                y = 1,
+                flip = "",
+                dxdy,
+                kx = zoom / sx,
+                ky = zoom / sy;
+            s.visibility = "hidden";
+            if (!sx || !sy) {
+                return;
+            }
+            o.coordsize = abs(kx) + S + abs(ky);
+            s.rotation = deg * (sx * sy < 0 ? -1 : 1);
+            if (deg) {
+                var c = compensation(deg, dx, dy);
+                dx = c.dx;
+                dy = c.dy;
+            }
+            sx < 0 && (flip += "x");
+            sy < 0 && (flip += " y") && (y = -1);
+            s.flip = flip;
+            o.coordorigin = (dx * -kx) + S + (dy * -ky);
+            if (fillpos || _.fillsize) {
+                var fill = o.getElementsByTagName(fillString);
+                fill = fill && fill[0];
+                o.removeChild(fill);
+                if (fillpos) {
+                    c = compensation(deg, m.x(fillpos[0], fillpos[1]), m.y(fillpos[0], fillpos[1]));
+                    fill.position = c.dx * y + S + c.dy * y;
+                }
+                if (_.fillsize) {
+                    fill.size = _.fillsize[0] * abs(sx) + S + _.fillsize[1] * abs(sy);
+                }
+                o.appendChild(fill);
+            }
+            s.visibility = "visible";
+        };
+    R.toString = function () {
+        return  "Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl " + this.version;
+    };
+    var addArrow = function (o, value, isEnd) {
+        var values = Str(value).toLowerCase().split("-"),
+            se = isEnd ? "end" : "start",
+            i = values.length,
+            type = "classic",
+            w = "medium",
+            h = "medium";
+        while (i--) {
+            switch (values[i]) {
+                case "block":
+                case "classic":
+                case "oval":
+                case "diamond":
+                case "open":
+                case "none":
+                    type = values[i];
+                    break;
+                case "wide":
+                case "narrow": h = values[i]; break;
+                case "long":
+                case "short": w = values[i]; break;
+            }
+        }
+        var stroke = o.node.getElementsByTagName("stroke")[0];
+        stroke[se + "arrow"] = type;
+        stroke[se + "arrowlength"] = w;
+        stroke[se + "arrowwidth"] = h;
+    },
+    setFillAndStroke = function (o, params) {
+        // o.paper.canvas.style.display = "none";
+        o.attrs = o.attrs || {};
+        var node = o.node,
+            a = o.attrs,
+            s = node.style,
+            xy,
+            newpath = pathTypes[o.type] && (params.x != a.x || params.y != a.y || params.width != a.width || params.height != a.height || params.cx != a.cx || params.cy != a.cy || params.rx != a.rx || params.ry != a.ry || params.r != a.r),
+            isOval = ovalTypes[o.type] && (a.cx != params.cx || a.cy != params.cy || a.r != params.r || a.rx != params.rx || a.ry != params.ry),
+            res = o;
+
+
+        for (var par in params) if (params[has](par)) {
+            a[par] = params[par];
+        }
+        if (newpath) {
+            a.path = R._getPath[o.type](o);
+            o._.dirty = 1;
+        }
+        params.href && (node.href = params.href);
+        params.title && (node.title = params.title);
+        params.target && (node.target = params.target);
+        params.cursor && (s.cursor = params.cursor);
+        "blur" in params && o.blur(params.blur);
+        if (params.path && o.type == "path" || newpath) {
+            node.path = path2vml(~Str(a.path).toLowerCase().indexOf("r") ? R._pathToAbsolute(a.path) : a.path);
+            if (o.type == "image") {
+                o._.fillpos = [a.x, a.y];
+                o._.fillsize = [a.width, a.height];
+                setCoords(o, 1, 1, 0, 0, 0);
+            }
+        }
+        "transform" in params && o.transform(params.transform);
+        if (isOval) {
+            var cx = +a.cx,
+                cy = +a.cy,
+                rx = +a.rx || +a.r || 0,
+                ry = +a.ry || +a.r || 0;
+            node.path = R.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x", round((cx - rx) * zoom), round((cy - ry) * zoom), round((cx + rx) * zoom), round((cy + ry) * zoom), round(cx * zoom));
+        }
+        if ("clip-rect" in params) {
+            var rect = Str(params["clip-rect"]).split(separator);
+            if (rect.length == 4) {
+                rect[2] = +rect[2] + (+rect[0]);
+                rect[3] = +rect[3] + (+rect[1]);
+                var div = node.clipRect || R._g.doc.createElement("div"),
+                    dstyle = div.style;
+                dstyle.clip = R.format("rect({1}px {2}px {3}px {0}px)", rect);
+                if (!node.clipRect) {
+                    dstyle.position = "absolute";
+                    dstyle.top = 0;
+                    dstyle.left = 0;
+                    dstyle.width = o.paper.width + "px";
+                    dstyle.height = o.paper.height + "px";
+                    node.parentNode.insertBefore(div, node);
+                    div.appendChild(node);
+                    node.clipRect = div;
+                }
+            }
+            if (!params["clip-rect"]) {
+                node.clipRect && (node.clipRect.style.clip = "auto");
+            }
+        }
+        if (o.textpath) {
+            var textpathStyle = o.textpath.style;
+            params.font && (textpathStyle.font = params.font);
+            params["font-family"] && (textpathStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g, E) + '"');
+            params["font-size"] && (textpathStyle.fontSize = params["font-size"]);
+            params["font-weight"] && (textpathStyle.fontWeight = params["font-weight"]);
+            params["font-style"] && (textpathStyle.fontStyle = params["font-style"]);
+        }
+        if ("arrow-start" in params) {
+            addArrow(res, params["arrow-start"]);
+        }
+        if ("arrow-end" in params) {
+            addArrow(res, params["arrow-end"], 1);
+        }
+        if (params.opacity != null || 
+            params["stroke-width"] != null ||
+            params.fill != null ||
+            params.src != null ||
+            params.stroke != null ||
+            params["stroke-width"] != null ||
+            params["stroke-opacity"] != null ||
+            params["fill-opacity"] != null ||
+            params["stroke-dasharray"] != null ||
+            params["stroke-miterlimit"] != null ||
+            params["stroke-linejoin"] != null ||
+            params["stroke-linecap"] != null) {
+            var fill = node.getElementsByTagName(fillString),
+                newfill = false;
+            fill = fill && fill[0];
+            !fill && (newfill = fill = createNode(fillString));
+            if (o.type == "image" && params.src) {
+                fill.src = params.src;
+            }
+            params.fill && (fill.on = true);
+            if (fill.on == null || params.fill == "none" || params.fill === null) {
+                fill.on = false;
+            }
+            if (fill.on && params.fill) {
+                var isURL = Str(params.fill).match(R._ISURL);
+                if (isURL) {
+                    fill.parentNode == node && node.removeChild(fill);
+                    fill.rotate = true;
+                    fill.src = isURL[1];
+                    fill.type = "tile";
+                    var bbox = o.getBBox(1);
+                    fill.position = bbox.x + S + bbox.y;
+                    o._.fillpos = [bbox.x, bbox.y];
+
+                    R._preload(isURL[1], function () {
+                        o._.fillsize = [this.offsetWidth, this.offsetHeight];
+                    });
+                } else {
+                    fill.color = R.getRGB(params.fill).hex;
+                    fill.src = E;
+                    fill.type = "solid";
+                    if (R.getRGB(params.fill).error && (res.type in {circle: 1, ellipse: 1} || Str(params.fill).charAt() != "r") && addGradientFill(res, params.fill, fill)) {
+                        a.fill = "none";
+                        a.gradient = params.fill;
+                        fill.rotate = false;
+                    }
+                }
+            }
+            if ("fill-opacity" in params || "opacity" in params) {
+                var opacity = ((+a["fill-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+R.getRGB(params.fill).o + 1 || 2) - 1);
+                opacity = mmin(mmax(opacity, 0), 1);
+                fill.opacity = opacity;
+                if (fill.src) {
+                    fill.color = "none";
+                }
+            }
+            node.appendChild(fill);
+            var stroke = (node.getElementsByTagName("stroke") && node.getElementsByTagName("stroke")[0]),
+            newstroke = false;
+            !stroke && (newstroke = stroke = createNode("stroke"));
+            if ((params.stroke && params.stroke != "none") ||
+                params["stroke-width"] ||
+                params["stroke-opacity"] != null ||
+                params["stroke-dasharray"] ||
+                params["stroke-miterlimit"] ||
+                params["stroke-linejoin"] ||
+                params["stroke-linecap"]) {
+                stroke.on = true;
+            }
+            (params.stroke == "none" || params.stroke === null || stroke.on == null || params.stroke == 0 || params["stroke-width"] == 0) && (stroke.on = false);
+            var strokeColor = R.getRGB(params.stroke);
+            stroke.on && params.stroke && (stroke.color = strokeColor.hex);
+            opacity = ((+a["stroke-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+strokeColor.o + 1 || 2) - 1);
+            var width = (toFloat(params["stroke-width"]) || 1) * .75;
+            opacity = mmin(mmax(opacity, 0), 1);
+            params["stroke-width"] == null && (width = a["stroke-width"]);
+            params["stroke-width"] && (stroke.weight = width);
+            width && width < 1 && (opacity *= width) && (stroke.weight = 1);
+            stroke.opacity = opacity;
+        
+            params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter");
+            stroke.miterlimit = params["stroke-miterlimit"] || 8;
+            params["stroke-linecap"] && (stroke.endcap = params["stroke-linecap"] == "butt" ? "flat" : params["stroke-linecap"] == "square" ? "square" : "round");
+            if (params["stroke-dasharray"]) {
+                var dasharray = {
+                    "-": "shortdash",
+                    ".": "shortdot",
+                    "-.": "shortdashdot",
+                    "-..": "shortdashdotdot",
+                    ". ": "dot",
+                    "- ": "dash",
+                    "--": "longdash",
+                    "- .": "dashdot",
+                    "--.": "longdashdot",
+                    "--..": "longdashdotdot"
+                };
+                stroke.dashstyle = dasharray[has](params["stroke-dasharray"]) ? dasharray[params["stroke-dasharray"]] : E;
+            }
+            newstroke && node.appendChild(stroke);
+        }
+        if (res.type == "text") {
+            res.paper.canvas.style.display = E;
+            var span = res.paper.span,
+                m = 100,
+                fontSize = a.font && a.font.match(/\d+(?:\.\d*)?(?=px)/);
+            s = span.style;
+            a.font && (s.font = a.font);
+            a["font-family"] && (s.fontFamily = a["font-family"]);
+            a["font-weight"] && (s.fontWeight = a["font-weight"]);
+            a["font-style"] && (s.fontStyle = a["font-style"]);
+            fontSize = toFloat(a["font-size"] || fontSize && fontSize[0]) || 10;
+            s.fontSize = fontSize * m + "px";
+            res.textpath.string && (span.innerHTML = Str(res.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>"));
+            var brect = span.getBoundingClientRect();
+            res.W = a.w = (brect.right - brect.left) / m;
+            res.H = a.h = (brect.bottom - brect.top) / m;
+            // res.paper.canvas.style.display = "none";
+            res.X = a.x;
+            res.Y = a.y + res.H / 2;
+
+            ("x" in params || "y" in params) && (res.path.v = R.format("m{0},{1}l{2},{1}", round(a.x * zoom), round(a.y * zoom), round(a.x * zoom) + 1));
+            var dirtyattrs = ["x", "y", "text", "font", "font-family", "font-weight", "font-style", "font-size"];
+            for (var d = 0, dd = dirtyattrs.length; d < dd; d++) if (dirtyattrs[d] in params) {
+                res._.dirty = 1;
+                break;
+            }
+        
+            // text-anchor emulation
+            switch (a["text-anchor"]) {
+                case "start":
+                    res.textpath.style["v-text-align"] = "left";
+                    res.bbx = res.W / 2;
+                break;
+                case "end":
+                    res.textpath.style["v-text-align"] = "right";
+                    res.bbx = -res.W / 2;
+                break;
+                default:
+                    res.textpath.style["v-text-align"] = "center";
+                    res.bbx = 0;
+                break;
+            }
+            res.textpath.style["v-text-kern"] = true;
+        }
+        // res.paper.canvas.style.display = E;
+    },
+    addGradientFill = function (o, gradient, fill) {
+        o.attrs = o.attrs || {};
+        var attrs = o.attrs,
+            pow = Math.pow,
+            opacity,
+            oindex,
+            type = "linear",
+            fxfy = ".5 .5";
+        o.attrs.gradient = gradient;
+        gradient = Str(gradient).replace(R._radial_gradient, function (all, fx, fy) {
+            type = "radial";
+            if (fx && fy) {
+                fx = toFloat(fx);
+                fy = toFloat(fy);
+                pow(fx - .5, 2) + pow(fy - .5, 2) > .25 && (fy = math.sqrt(.25 - pow(fx - .5, 2)) * ((fy > .5) * 2 - 1) + .5);
+                fxfy = fx + S + fy;
+            }
+            return E;
+        });
+        gradient = gradient.split(/\s*\-\s*/);
+        if (type == "linear") {
+            var angle = gradient.shift();
+            angle = -toFloat(angle);
+            if (isNaN(angle)) {
+                return null;
+            }
+        }
+        var dots = R._parseDots(gradient);
+        if (!dots) {
+            return null;
+        }
+        o = o.shape || o.node;
+        if (dots.length) {
+            o.removeChild(fill);
+            fill.on = true;
+            fill.method = "none";
+            fill.color = dots[0].color;
+            fill.color2 = dots[dots.length - 1].color;
+            var clrs = [];
+            for (var i = 0, ii = dots.length; i < ii; i++) {
+                dots[i].offset && clrs.push(dots[i].offset + S + dots[i].color);
+            }
+            fill.colors = clrs.length ? clrs.join() : "0% " + fill.color;
+            if (type == "radial") {
+                fill.type = "gradientTitle";
+                fill.focus = "100%";
+                fill.focussize = "0 0";
+                fill.focusposition = fxfy;
+                fill.angle = 0;
+            } else {
+                // fill.rotate= true;
+                fill.type = "gradient";
+                fill.angle = (270 - angle) % 360;
+            }
+            o.appendChild(fill);
+        }
+        return 1;
+    },
+    Element = function (node, vml) {
+        this[0] = this.node = node;
+        node.raphael = true;
+        this.id = R._oid++;
+        node.raphaelid = this.id;
+        this.X = 0;
+        this.Y = 0;
+        this.attrs = {};
+        this.paper = vml;
+        this.matrix = R.matrix();
+        this._ = {
+            transform: [],
+            sx: 1,
+            sy: 1,
+            dx: 0,
+            dy: 0,
+            deg: 0,
+            dirty: 1,
+            dirtyT: 1
+        };
+        !vml.bottom && (vml.bottom = this);
+        this.prev = vml.top;
+        vml.top && (vml.top.next = this);
+        vml.top = this;
+        this.next = null;
+    };
+    var elproto = R.el;
+
+    Element.prototype = elproto;
+    elproto.constructor = Element;
+    elproto.transform = function (tstr) {
+        if (tstr == null) {
+            return this._.transform;
+        }
+        var vbs = this.paper._viewBoxShift,
+            vbt = vbs ? "s" + [vbs.scale, vbs.scale] + "-1-1t" + [vbs.dx, vbs.dy] : E,
+            oldt;
+        if (vbs) {
+            oldt = tstr = Str(tstr).replace(/\.{3}|\u2026/g, this._.transform || E);
+        }
+        R._extractTransform(this, vbt + tstr);
+        var matrix = this.matrix.clone(),
+            skew = this.skew,
+            o = this.node,
+            split,
+            isGrad = ~Str(this.attrs.fill).indexOf("-"),
+            isPatt = !Str(this.attrs.fill).indexOf("url(");
+        matrix.translate(-.5, -.5);
+        if (isPatt || isGrad || this.type == "image") {
+            skew.matrix = "1 0 0 1";
+            skew.offset = "0 0";
+            split = matrix.split();
+            if ((isGrad && split.noRotation) || !split.isSimple) {
+                o.style.filter = matrix.toFilter();
+                var bb = this.getBBox(),
+                    bbt = this.getBBox(1),
+                    dx = bb.x - bbt.x,
+                    dy = bb.y - bbt.y;
+                o.coordorigin = (dx * -zoom) + S + (dy * -zoom);
+                setCoords(this, 1, 1, dx, dy, 0);
+            } else {
+                o.style.filter = E;
+                setCoords(this, split.scalex, split.scaley, split.dx, split.dy, split.rotate);
+            }
+        } else {
+            o.style.filter = E;
+            skew.matrix = Str(matrix);
+            skew.offset = matrix.offset();
+        }
+        oldt && (this._.transform = oldt);
+        return this;
+    };
+    elproto.rotate = function (deg, cx, cy) {
+        if (this.removed) {
+            return this;
+        }
+        if (deg == null) {
+            return;
+        }
+        deg = Str(deg).split(separator);
+        if (deg.length - 1) {
+            cx = toFloat(deg[1]);
+            cy = toFloat(deg[2]);
+        }
+        deg = toFloat(deg[0]);
+        (cy == null) && (cx = cy);
+        if (cx == null || cy == null) {
+            var bbox = this.getBBox(1);
+            cx = bbox.x + bbox.width / 2;
+            cy = bbox.y + bbox.height / 2;
+        }
+        this._.dirtyT = 1;
+        this.transform(this._.transform.concat([["r", deg, cx, cy]]));
+        return this;
+    };
+    elproto.translate = function (dx, dy) {
+        if (this.removed) {
+            return this;
+        }
+        dx = Str(dx).split(separator);
+        if (dx.length - 1) {
+            dy = toFloat(dx[1]);
+        }
+        dx = toFloat(dx[0]) || 0;
+        dy = +dy || 0;
+        if (this._.bbox) {
+            this._.bbox.x += dx;
+            this._.bbox.y += dy;
+        }
+        this.transform(this._.transform.concat([["t", dx, dy]]));
+        return this;
+    };
+    elproto.scale = function (sx, sy, cx, cy) {
+        if (this.removed) {
+            return this;
+        }
+        sx = Str(sx).split(separator);
+        if (sx.length - 1) {
+            sy = toFloat(sx[1]);
+            cx = toFloat(sx[2]);
+            cy = toFloat(sx[3]);
+            isNaN(cx) && (cx = null);
+            isNaN(cy) && (cy = null);
+        }
+        sx = toFloat(sx[0]);
+        (sy == null) && (sy = sx);
+        (cy == null) && (cx = cy);
+        if (cx == null || cy == null) {
+            var bbox = this.getBBox(1);
+        }
+        cx = cx == null ? bbox.x + bbox.width / 2 : cx;
+        cy = cy == null ? bbox.y + bbox.height / 2 : cy;
+    
+        this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
+        this._.dirtyT = 1;
+        return this;
+    };
+    elproto.hide = function () {
+        !this.removed && (this.node.style.display = "none");
+        return this;
+    };
+    elproto.show = function () {
+        !this.removed && (this.node.style.display = E);
+        return this;
+    };
+    elproto._getBBox = function () {
+        if (this.removed) {
+            return {};
+        }
+        return {
+            x: this.X + (this.bbx || 0) - this.W / 2,
+            y: this.Y - this.H,
+            width: this.W,
+            height: this.H
+        };
+    };
+    elproto.remove = function () {
+        if (this.removed || !this.node.parentNode) {
+            return;
+        }
+        this.paper.__set__ && this.paper.__set__.exclude(this);
+        R.eve.unbind("raphael.*.*." + this.id);
+        R._tear(this, this.paper);
+        this.node.parentNode.removeChild(this.node);
+        this.shape && this.shape.parentNode.removeChild(this.shape);
+        for (var i in this) {
+            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
+        }
+        this.removed = true;
+    };
+    elproto.attr = function (name, value) {
+        if (this.removed) {
+            return this;
+        }
+        if (name == null) {
+            var res = {};
+            for (var a in this.attrs) if (this.attrs[has](a)) {
+                res[a] = this.attrs[a];
+            }
+            res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
+            res.transform = this._.transform;
+            return res;
+        }
+        if (value == null && R.is(name, "string")) {
+            if (name == fillString && this.attrs.fill == "none" && this.attrs.gradient) {
+                return this.attrs.gradient;
+            }
+            var names = name.split(separator),
+                out = {};
+            for (var i = 0, ii = names.length; i < ii; i++) {
+                name = names[i];
+                if (name in this.attrs) {
+                    out[name] = this.attrs[name];
+                } else if (R.is(this.paper.customAttributes[name], "function")) {
+                    out[name] = this.paper.customAttributes[name].def;
+                } else {
+                    out[name] = R._availableAttrs[name];
+                }
+            }
+            return ii - 1 ? out : out[names[0]];
+        }
+        if (this.attrs && value == null && R.is(name, "array")) {
+            out = {};
+            for (i = 0, ii = name.length; i < ii; i++) {
+                out[name[i]] = this.attr(name[i]);
+            }
+            return out;
+        }
+        var params;
+        if (value != null) {
+            params = {};
+            params[name] = value;
+        }
+        value == null && R.is(name, "object") && (params = name);
+        for (var key in params) {
+            eve("raphael.attr." + key + "." + this.id, this, params[key]);
+        }
+        if (params) {
+            for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
+                var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
+                this.attrs[key] = params[key];
+                for (var subkey in par) if (par[has](subkey)) {
+                    params[subkey] = par[subkey];
+                }
+            }
+            // this.paper.canvas.style.display = "none";
+            if (params.text && this.type == "text") {
+                this.textpath.string = params.text;
+            }
+            setFillAndStroke(this, params);
+            // this.paper.canvas.style.display = E;
+        }
+        return this;
+    };
+    elproto.toFront = function () {
+        !this.removed && this.node.parentNode.appendChild(this.node);
+        this.paper && this.paper.top != this && R._tofront(this, this.paper);
+        return this;
+    };
+    elproto.toBack = function () {
+        if (this.removed) {
+            return this;
+        }
+        if (this.node.parentNode.firstChild != this.node) {
+            this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild);
+            R._toback(this, this.paper);
+        }
+        return this;
+    };
+    elproto.insertAfter = function (element) {
+        if (this.removed) {
+            return this;
+        }
+        if (element.constructor == R.st.constructor) {
+            element = element[element.length - 1];
+        }
+        if (element.node.nextSibling) {
+            element.node.parentNode.insertBefore(this.node, element.node.nextSibling);
+        } else {
+            element.node.parentNode.appendChild(this.node);
+        }
+        R._insertafter(this, element, this.paper);
+        return this;
+    };
+    elproto.insertBefore = function (element) {
+        if (this.removed) {
+            return this;
+        }
+        if (element.constructor == R.st.constructor) {
+            element = element[0];
+        }
+        element.node.parentNode.insertBefore(this.node, element.node);
+        R._insertbefore(this, element, this.paper);
+        return this;
+    };
+    elproto.blur = function (size) {
+        var s = this.node.runtimeStyle,
+            f = s.filter;
+        f = f.replace(blurregexp, E);
+        if (+size !== 0) {
+            this.attrs.blur = size;
+            s.filter = f + S + ms + ".Blur(pixelradius=" + (+size || 1.5) + ")";
+            s.margin = R.format("-{0}px 0 0 -{0}px", round(+size || 1.5));
+        } else {
+            s.filter = f;
+            s.margin = 0;
+            delete this.attrs.blur;
+        }
+    };
+
+    R._engine.path = function (pathString, vml) {
+        var el = createNode("shape");
+        el.style.cssText = cssDot;
+        el.coordsize = zoom + S + zoom;
+        el.coordorigin = vml.coordorigin;
+        var p = new Element(el, vml),
+            attr = {fill: "none", stroke: "#000"};
+        pathString && (attr.path = pathString);
+        p.type = "path";
+        p.path = [];
+        p.Path = E;
+        setFillAndStroke(p, attr);
+        vml.canvas.appendChild(el);
+        var skew = createNode("skew");
+        skew.on = true;
+        el.appendChild(skew);
+        p.skew = skew;
+        p.transform(E);
+        return p;
+    };
+    R._engine.rect = function (vml, x, y, w, h, r) {
+        var path = R._rectPath(x, y, w, h, r),
+            res = vml.path(path),
+            a = res.attrs;
+        res.X = a.x = x;
+        res.Y = a.y = y;
+        res.W = a.width = w;
+        res.H = a.height = h;
+        a.r = r;
+        a.path = path;
+        res.type = "rect";
+        return res;
+    };
+    R._engine.ellipse = function (vml, x, y, rx, ry) {
+        var res = vml.path(),
+            a = res.attrs;
+        res.X = x - rx;
+        res.Y = y - ry;
+        res.W = rx * 2;
+        res.H = ry * 2;
+        res.type = "ellipse";
+        setFillAndStroke(res, {
+            cx: x,
+            cy: y,
+            rx: rx,
+            ry: ry
+        });
+        return res;
+    };
+    R._engine.circle = function (vml, x, y, r) {
+        var res = vml.path(),
+            a = res.attrs;
+        res.X = x - r;
+        res.Y = y - r;
+        res.W = res.H = r * 2;
+        res.type = "circle";
+        setFillAndStroke(res, {
+            cx: x,
+            cy: y,
+            r: r
+        });
+        return res;
+    };
+    R._engine.image = function (vml, src, x, y, w, h) {
+        var path = R._rectPath(x, y, w, h),
+            res = vml.path(path).attr({stroke: "none"}),
+            a = res.attrs,
+            node = res.node,
+            fill = node.getElementsByTagName(fillString)[0];
+        a.src = src;
+        res.X = a.x = x;
+        res.Y = a.y = y;
+        res.W = a.width = w;
+        res.H = a.height = h;
+        a.path = path;
+        res.type = "image";
+        fill.parentNode == node && node.removeChild(fill);
+        fill.rotate = true;
+        fill.src = src;
+        fill.type = "tile";
+        res._.fillpos = [x, y];
+        res._.fillsize = [w, h];
+        node.appendChild(fill);
+        setCoords(res, 1, 1, 0, 0, 0);
+        return res;
+    };
+    R._engine.text = function (vml, x, y, text) {
+        var el = createNode("shape"),
+            path = createNode("path"),
+            o = createNode("textpath");
+        x = x || 0;
+        y = y || 0;
+        text = text || "";
+        path.v = R.format("m{0},{1}l{2},{1}", round(x * zoom), round(y * zoom), round(x * zoom) + 1);
+        path.textpathok = true;
+        o.string = Str(text);
+        o.on = true;
+        el.style.cssText = cssDot;
+        el.coordsize = zoom + S + zoom;
+        el.coordorigin = "0 0";
+        var p = new Element(el, vml),
+            attr = {
+                fill: "#000",
+                stroke: "none",
+                font: R._availableAttrs.font,
+                text: text
+            };
+        p.shape = el;
+        p.path = path;
+        p.textpath = o;
+        p.type = "text";
+        p.attrs.text = Str(text);
+        p.attrs.x = x;
+        p.attrs.y = y;
+        p.attrs.w = 1;
+        p.attrs.h = 1;
+        setFillAndStroke(p, attr);
+        el.appendChild(o);
+        el.appendChild(path);
+        vml.canvas.appendChild(el);
+        var skew = createNode("skew");
+        skew.on = true;
+        el.appendChild(skew);
+        p.skew = skew;
+        p.transform(E);
+        return p;
+    };
+    R._engine.setSize = function (width, height) {
+        var cs = this.canvas.style;
+        this.width = width;
+        this.height = height;
+        width == +width && (width += "px");
+        height == +height && (height += "px");
+        cs.width = width;
+        cs.height = height;
+        cs.clip = "rect(0 " + width + " " + height + " 0)";
+        if (this._viewBox) {
+            R._engine.setViewBox.apply(this, this._viewBox);
+        }
+        return this;
+    };
+    R._engine.setViewBox = function (x, y, w, h, fit) {
+        R.eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
+        var width = this.width,
+            height = this.height,
+            size = 1 / mmax(w / width, h / height),
+            H, W;
+        if (fit) {
+            H = height / h;
+            W = width / w;
+            if (w * H < width) {
+                x -= (width - w * H) / 2 / H;
+            }
+            if (h * W < height) {
+                y -= (height - h * W) / 2 / W;
+            }
+        }
+        this._viewBox = [x, y, w, h, !!fit];
+        this._viewBoxShift = {
+            dx: -x,
+            dy: -y,
+            scale: size
+        };
+        this.forEach(function (el) {
+            el.transform("...");
+        });
+        return this;
+    };
+    var createNode;
+    R._engine.initWin = function (win) {
+            var doc = win.document;
+            doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
+            try {
+                !doc.namespaces.rvml && doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
+                createNode = function (tagName) {
+                    return doc.createElement('<rvml:' + tagName + ' class="rvml">');
+                };
+            } catch (e) {
+                createNode = function (tagName) {
+                    return doc.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
+                };
+            }
+        };
+    R._engine.initWin(R._g.win);
+    R._engine.create = function () {
+        var con = R._getContainer.apply(0, arguments),
+            container = con.container,
+            height = con.height,
+            s,
+            width = con.width,
+            x = con.x,
+            y = con.y;
+        if (!container) {
+            throw new Error("VML container not found.");
+        }
+        var res = new R._Paper,
+            c = res.canvas = R._g.doc.createElement("div"),
+            cs = c.style;
+        x = x || 0;
+        y = y || 0;
+        width = width || 512;
+        height = height || 342;
+        res.width = width;
+        res.height = height;
+        width == +width && (width += "px");
+        height == +height && (height += "px");
+        res.coordsize = zoom * 1e3 + S + zoom * 1e3;
+        res.coordorigin = "0 0";
+        res.span = R._g.doc.createElement("span");
+        res.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;";
+        c.appendChild(res.span);
+        cs.cssText = R.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden", width, height);
+        if (container == 1) {
+            R._g.doc.body.appendChild(c);
+            cs.left = x + "px";
+            cs.top = y + "px";
+            cs.position = "absolute";
+        } else {
+            if (container.firstChild) {
+                container.insertBefore(c, container.firstChild);
+            } else {
+                container.appendChild(c);
+            }
+        }
+        res.renderfix = function () {};
+        return res;
+    };
+    R.prototype.clear = function () {
+        R.eve("raphael.clear", this);
+        this.canvas.innerHTML = E;
+        this.span = R._g.doc.createElement("span");
+        this.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";
+        this.canvas.appendChild(this.span);
+        this.bottom = this.top = null;
+    };
+    R.prototype.remove = function () {
+        R.eve("raphael.remove", this);
+        this.canvas.parentNode.removeChild(this.canvas);
+        for (var i in this) {
+            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
+        }
+        return true;
+    };
+
+    var setproto = R.st;
+    for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
+        setproto[method] = (function (methodname) {
+            return function () {
+                var arg = arguments;
+                return this.forEach(function (el) {
+                    el[methodname].apply(el, arg);
+                });
+            };
+        })(method);
+    }
+}(window.Raphael);

+ 441 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/js/textlayout.js

@@ -0,0 +1,441 @@
+	window.onload = function () {
+			var paper = Raphael("holder");
+
+			//var curve = paper.ellipse(100, 100, 1, 1).attr({"stroke-width": 0, fill: Color.red});
+			
+			var text = "Betty Botter bought some butter but, she said, the butter's bitter. If I put it in my batter, it will make my batter bitter. But a bit of better butter will make my batter better. So, she bought a bit of butter, better than her bitter butter, and she put it in her batter, and the batter was not bitter. It was better Betty Botter bought a bit better butter.";
+			var font = {font: "11px Arial", "font-style":"italic", opacity: 1, "fill": LABEL_COLOR, stroke: LABEL_COLOR, "stroke-width":.3};
+			var font = {font: "11px Arial", opacity: 1, "fill": LABEL_COLOR};
+			var boxWidth = 100
+			
+			var AttributedStringIterator = function(text){
+				//this.text = this.rtrim(this.ltrim(text));
+				text = text.replace(/(\s)+/, " ");
+				this.text = this.rtrim(text);
+				/*
+				if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) {
+					throw new IllegalArgumentException("Invalid substring range");
+				}
+				*/
+				this.beginIndex = 0;
+				this.endIndex = this.text.length;
+				this.currentIndex = this.beginIndex;
+				
+				//console.group("[AttributedStringIterator]");
+				var i = 0;
+				var string = this.text;
+				var fullPos = 0;
+				
+				//console.log("string: \"" + string + "\", length: " + string.length);
+				this.startWordOffsets = [];
+				this.startWordOffsets.push(fullPos);
+				
+				// TODO: remove i 1000
+				while (i<1000) {
+					var pos = string.search(/[ \t\n\f-\.\,]/);
+					if (pos == -1)
+						break;
+					
+					// whitespace start
+					fullPos += pos;
+					string = string.substr(pos);
+					////console.log("fullPos: " + fullPos + ", pos: " + pos +  ", string: ", string);
+					
+					// remove whitespaces
+					var pos = string.search(/[^ \t\n\f-\.\,]/);
+					if (pos == -1)
+						break;
+						
+					// whitespace end
+					fullPos += pos;
+					string = string.substr(pos);
+					
+					////console.log("fullPos: " + fullPos);
+					this.startWordOffsets.push(fullPos);
+					
+					i++;
+				}
+				//console.log("startWordOffsets: ", this.startWordOffsets);
+				//console.groupEnd();
+			};
+			AttributedStringIterator.prototype = {
+				getEndIndex: function(pos){
+					if (typeof(pos) == "undefined")
+						return this.endIndex;
+						
+					var string = this.text.substr(pos, this.endIndex - pos);
+					
+					var posEndOfLine = string.search(/[\n]/);
+					if (posEndOfLine == -1)
+						return this.endIndex;
+					else
+						return pos + posEndOfLine;
+				},
+				getBeginIndex: function(){
+					return this.beginIndex;
+				},
+				isWhitespace: function(pos){
+					var str = this.text[pos];
+					var whitespaceChars = " \t\n\f";
+					
+					return (whitespaceChars.indexOf(str) != -1);
+				},
+				isNewLine: function(pos){
+					var str = this.text[pos];
+					var whitespaceChars = "\n";
+					
+					return (whitespaceChars.indexOf(str) != -1);
+				},
+				preceding: function(pos){
+					//console.group("[AttributedStringIterator.preceding]");
+					for(var i in this.startWordOffsets) {
+						var startWordOffset = this.startWordOffsets[i];
+						if (pos < startWordOffset && i>0) {
+							//console.log("startWordOffset: " + this.startWordOffsets[i-1]);
+							//console.groupEnd();
+							return this.startWordOffsets[i-1];
+						}
+					}
+					//console.log("pos: " + pos);
+					//console.groupEnd();
+					return this.startWordOffsets[i];
+				},
+				following: function(pos){
+					//console.group("[AttributedStringIterator.following]");
+					for(var i in this.startWordOffsets) {
+						var startWordOffset = this.startWordOffsets[i];
+						if (pos < startWordOffset && i>0) {
+							//console.log("startWordOffset: " + this.startWordOffsets[i]);
+							//console.groupEnd();
+							return this.startWordOffsets[i];
+						}
+					}
+					//console.log("pos: " + pos);
+					//console.groupEnd();
+					return this.startWordOffsets[i];
+				},
+				ltrim: function(str){
+					var patt2=/^\s+/g;
+					return str.replace(patt2, "");
+				}, 
+				rtrim: function(str){
+					var patt2=/\s+$/g;
+					return str.replace(patt2, "");
+				},
+				getLayout: function(start, limit){
+					return this.text.substr(start, limit - start);
+				},
+				getCharAtPos: function(pos) {
+					return this.text[pos];
+				}
+			};
+			
+			/*
+			var TextMeasurer = function(paper, text, fontAttrs){
+				this.text = text;
+				this.paper = paper;
+				this.fontAttrs = fontAttrs;
+				
+				this.fStart = this.text.getBeginIndex();
+
+			};
+			TextMeasurer.prototype = {
+				getLineBreakIndex: function(start, maxAdvance){
+					var localStart = start - this.fStart;
+				},
+				getLayout: function(){
+				}
+			}
+			*/
+			
+			
+			var LineBreakMeasurer = function(paper, text, fontAttrs){
+				this.paper = paper;
+				this.text = new AttributedStringIterator(text);
+				this.fontAttrs = fontAttrs;
+				
+				if (this.text.getEndIndex() - this.text.getBeginIndex() < 1) {
+					throw {message: "Text must contain at least one character.", code: "IllegalArgumentException"};
+				}
+				
+				//this.measurer = new TextMeasurer(paper, this.text, this.fontAttrs);
+				this.limit = this.text.getEndIndex();
+				this.pos = this.start = this.text.getBeginIndex();
+				
+				this.rafaelTextObject = this.paper.text(100, 100, this.text.text).attr(fontAttrs).attr("text-anchor", "start");
+				this.svgTextObject = this.rafaelTextObject[0];
+			};
+			LineBreakMeasurer.prototype = {
+				nextOffset: function(wrappingWidth, offsetLimit, requireNextWord) {
+					//console.group("[nextOffset]");
+					var nextOffset = this.pos;
+					if (this.pos < this.limit) {
+						if (offsetLimit <= this.pos) {
+							throw {message: "offsetLimit must be after current position", code: "IllegalArgumentException"};
+						}
+						
+						var charAtMaxAdvance = this.getLineBreakIndex(this.pos, wrappingWidth);
+						//charAtMaxAdvance --;
+						//console.log("charAtMaxAdvance:", charAtMaxAdvance, ", [" + this.text.getCharAtPos(charAtMaxAdvance) + "]");
+						
+						if (charAtMaxAdvance == this.limit) {
+							nextOffset = this.limit;
+							//console.log("charAtMaxAdvance == this.limit");
+						} else if (this.text.isNewLine(charAtMaxAdvance)) {
+							console.log("isNewLine");
+							nextOffset = charAtMaxAdvance+1;
+						} else if (this.text.isWhitespace(charAtMaxAdvance)) {
+							// TODO: find next noSpaceChar
+							//return nextOffset;
+							nextOffset = this.text.following(charAtMaxAdvance);
+						} else {
+							// Break is in a word;  back up to previous break.
+							/*
+							var testPos = charAtMaxAdvance + 1;
+							if (testPos == this.limit) {
+								console.error("hbz...");
+							} else {
+								nextOffset = this.text.preceding(charAtMaxAdvance);
+							}
+							*/
+							nextOffset = this.text.preceding(charAtMaxAdvance);
+							
+							if (nextOffset <= this.pos) {
+								nextOffset = Math.max(this.pos+1, charAtMaxAdvance);
+							}
+						}
+					}
+					if (nextOffset > offsetLimit) {
+						nextOffset = offsetLimit;
+					}
+					//console.log("nextOffset: " + nextOffset);
+					//console.groupEnd();
+					return nextOffset;
+				},
+				nextLayout: function(wrappingWidth) {
+					//console.groupCollapsed("[nextLayout]");
+					if (this.pos < this.limit) {
+						var requireNextWord = false;
+						var layoutLimit = this.nextOffset(wrappingWidth, this.limit, requireNextWord);
+						//console.log("layoutLimit:", layoutLimit);
+						if (layoutLimit == this.pos) {
+							//console.groupEnd();
+							return null;
+						}
+						var result = this.text.getLayout(this.pos, layoutLimit);
+						//console.log("layout: \"" + result + "\"");
+						
+						// remove end of line
+						
+						//var posEndOfLine = this.text.getEndIndex(this.pos);
+						//if (posEndOfLine < result.length)
+						//	result = result.substr(0, posEndOfLine);
+						
+						this.pos = layoutLimit;
+						
+						//console.groupEnd();
+						return result;
+					} else {
+						//console.groupEnd();
+						return null;
+					}
+				},
+				getLineBreakIndex: function(pos, wrappingWidth) {
+					//console.group("[getLineBreakIndex]");
+					//console.log("pos:"+pos + ", text: \""+ this.text.text.replace(/\n/g, "_").substr(pos, 1) + "\"");
+					
+					var bb = this.rafaelTextObject.getBBox();
+					
+					var charNum = -1;
+					try {
+						var svgPoint = this.svgTextObject.getStartPositionOfChar(pos);
+						//var dot = this.paper.ellipse(svgPoint.x, svgPoint.y, 1, 1).attr({"stroke-width": 0, fill: Color.blue});
+						svgPoint.x = svgPoint.x + wrappingWidth;
+						//svgPoint.y = bb.y;
+						//console.log("svgPoint:", svgPoint);
+					
+						//var dot = this.paper.ellipse(svgPoint.x, svgPoint.y, 1, 1).attr({"stroke-width": 0, fill: Color.red});
+					
+						charNum = this.svgTextObject.getCharNumAtPosition(svgPoint);
+					} catch (e){
+						console.warn("getStartPositionOfChar error, pos:" + pos);
+						/*
+						var testPos = pos + 1;
+						if (testPos < this.limit) {
+							return testPos
+						}
+						*/
+					}
+					//console.log("charNum:", charNum);
+					if (charNum == -1) {
+						//console.groupEnd();
+						return this.text.getEndIndex(pos);
+					} else {
+						// When case there is new line between pos and charnum then use this new line
+						var newLineIndex = this.text.getEndIndex(pos);
+						if (newLineIndex < charNum ) {
+							console.log("newLineIndex <= charNum, newLineIndex:"+newLineIndex+", charNum:"+charNum, "\"" + this.text.text.substr(newLineIndex+1).replace(/\n/g, "↵") + "\"");
+							//console.groupEnd();
+							
+							return newLineIndex;
+						}
+							
+						//var charAtMaxAdvance  = this.text.text.substring(charNum, charNum + 1);
+						var charAtMaxAdvance  = this.text.getCharAtPos(charNum);
+						//console.log("!!charAtMaxAdvance: " + charAtMaxAdvance);
+						//console.groupEnd();
+						return charNum;
+					}
+				}, 
+				getPosition: function() {
+					return this.pos;
+				}
+			};
+			
+			
+			
+			// ******
+			function drawMultilineText(text, x, y, boxWidth, boxHeight, options) {
+				var TEXT_PADDING = 3;
+				var width = boxWidth - (2 * TEXT_PADDING);
+				if (boxHeight)
+					var height = boxHeight - (2 * TEXT_PADDING);
+			
+				var layouts = [];
+				
+				var measurer = new LineBreakMeasurer(paper, text, font);
+				var lineHeight = measurer.rafaelTextObject.getBBox().height;
+				console.log("text: ", text.replace(/\n/g, "↵"));
+				
+				if (height) {
+					var availableLinesCount = parseInt(height/lineHeight);
+					console.log("availableLinesCount: " + availableLinesCount);
+				}
+				
+				var i = 1;
+				while (measurer.getPosition() < measurer.text.getEndIndex()) {
+					var layout = measurer.nextLayout(width);
+					//console.log("LAYOUT: " + layout + ", getPosition: " + measurer.getPosition());
+					
+					if (layout != null) {
+						if (!availableLinesCount || i < availableLinesCount) {
+							layouts.push(layout);
+						} else {
+							layouts.push(fitTextToWidth(layout + "...", boxWidth));
+							break;
+						}
+					}
+					i++;
+				};
+				console.log(layouts);
+				
+				measurer.rafaelTextObject.attr({"text": layouts.join("\n")});
+				//measurer.rafaelTextObject.attr({"text-anchor": "end"});
+				//measurer.rafaelTextObject.attr({"text-anchor": "middle"});
+				if (options)
+					measurer.rafaelTextObject.attr({"text-anchor": options["text-anchor"]});
+					
+				var bb = measurer.rafaelTextObject.getBBox();
+				//measurer.rafaelTextObject.attr({"x": x + boxWidth/2});
+				if (options["vertical-align"] == "top")
+					measurer.rafaelTextObject.attr({"y": y + bb.height/2 + TEXT_PADDING});
+				else
+					measurer.rafaelTextObject.attr({"y": y + height/2});
+				//var bb = measurer.rafaelTextObject.getBBox();
+				
+				if (measurer.rafaelTextObject.attr("text-anchor") == "middle" )
+					measurer.rafaelTextObject.attr("x",  x + boxWidth/2 + TEXT_PADDING/2);
+				else if (measurer.rafaelTextObject.attr("text-anchor") == "end" )
+					measurer.rafaelTextObject.attr("x",  x + boxWidth + TEXT_PADDING/2);
+				else 
+					measurer.rafaelTextObject.attr("x", x + boxWidth/2 - bb.width/2 + TEXT_PADDING/2);
+				
+				var boxStyle = {stroke: Color.LightSteelBlue2, "stroke-width": 1.0, "stroke-dasharray": "- "};
+				/*
+				var box = paper.rect(x+.0 + boxWidth/2 - bb.width/2+ TEXT_PADDING/2, y + .5 + boxHeight/2 - bb.height/2, width, height).attr(boxStyle);
+				box.attr("height", bb.height);
+				*/
+				//var box = paper.rect(bb.x - .5 + bb.width/2 + TEXT_PADDING, bb.y + bb.height/2, bb.width, bb.height).attr(boxStyle);
+				
+				var textAreaCX = x + boxWidth/2;
+				var textAreaCY = y + height/2;
+				var dotLeftTop = paper.ellipse(x, y, 3, 3).attr({"stroke-width": 0, fill: Color.LightSteelBlue, stroke: "none"});
+				var dotCenter = paper.ellipse(textAreaCX, textAreaCY, 3, 3).attr({fill: Color.LightSteelBlue2, stroke: "none"});
+
+				/*
+				// real bbox
+				var bb = measurer.rafaelTextObject.getBBox();
+				var rect = paper.rect(bb.x+.5, bb.y + .5, bb.width, bb.height).attr({"stroke-width": 1});
+				*/
+				var boxStyle = {stroke: Color.LightSteelBlue2, "stroke-width": 1.0, "stroke-dasharray": "- "};
+				var rect = paper.rect(x+.5, y + .5, boxWidth, boxHeight).attr(boxStyle);
+			}
+			
+			
+			
+			
+			/*
+			for (var i=0; i<1; i++) {
+				var t = text;
+				//var t = "Высококвалифицирова";
+				
+				var text = paper.text(300, 100, t).attr(font).attr("text-anchor", "start");
+				var bbText = text.getBBox();
+				paper.rect(300+.5, 100 + .5, bbText.width, bbText.height).attr({"stroke-width": 1});
+				console.log("t: ", t.replace(/\n/g, "↵"));
+				
+				while (measurer.getPosition() < measurer.text.getEndIndex()) {
+					var layout = measurer.nextLayout(width);
+					//console.log("LAYOUT: " + layout + ", getPosition: " + measurer.getPosition());
+					if (layout != null)
+						layouts.push(layout);
+				};
+				
+				measurer.rafaelTextObject.attr("text", layouts.join("\n"));
+				var bb = measurer.rafaelTextObject.getBBox();
+				var rect = paper.rect(bb.x+.5, bb.y + .5, bb.width, bb.height).attr({"stroke-width": 1});
+				
+				lay.push(layouts);
+				console.log(layouts);
+			}
+			*/
+			
+			
+			var fitTextToWidth = function(original, width) {
+				var text = original;
+
+				// TODO: move attr on parameters
+				var attr = {font: "11px Arial", opacity: 0};
+				
+				// remove length for "..."
+				var dots = paper.text(0, 0, "...").attr(attr).hide();
+				var dotsBB = dots.getBBox();
+				
+				var maxWidth = width - dotsBB.width;
+				
+				var textElement = paper.text(0, 0, text).attr(attr).hide();
+				var bb = textElement.getBBox();
+				
+				// it's a little bit incorrect with "..."
+				while (bb.width > maxWidth && text.length > 0) {
+					text = text.substring(0, text.length - 1);
+					textElement.attr({"text": text});
+					bb = textElement.getBBox();
+				}
+
+				// remove element from paper
+				textElement.remove();
+				
+				if (text != original) {
+					text = text + "...";
+				}
+
+				return text;
+			}
+			
+			
+			var x=100, y=90, height=20;
+			var options = {"text-anchor": "middle", "boxHeight": 150, "vertical-align": "top"};
+			var options = {"boxHeight": 150, "vertical-align": "top"};
+			drawMultilineText(text, x, y, 150, 100, options);
+	};

File diff suppressed because it is too large
+ 0 - 0
eladmin-activity/src/main/resources/static/diagram-viewer/style.css


+ 26 - 0
eladmin-activity/src/main/resources/static/editor-app/app-cfg.js

@@ -0,0 +1,26 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+'use strict';
+
+var ACTIVITI = ACTIVITI || {};
+
+ACTIVITI.CONFIG = {
+	// 'contextRoot' : '/activiti-explorer/service',
+	'contextRoot' : '/api/activiti',
+};

+ 433 - 0
eladmin-activity/src/main/resources/static/editor-app/app.js

@@ -0,0 +1,433 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+'use strict';
+
+var activitiModeler = angular.module('activitiModeler', [
+  'ngCookies',
+  'ngResource',
+  'ngSanitize',
+  'ngRoute',
+  'ngDragDrop',
+  'mgcrea.ngStrap', 
+  'ngGrid',
+  'ngAnimate',
+  'pascalprecht.translate',
+  'duScroll'
+]);
+
+var activitiModule = activitiModeler;
+
+activitiModeler
+  // Initialize routes
+  .config(['$selectProvider', '$translateProvider', function ($selectProvider, $translateProvider) {
+
+      // Override caret for bs-select directive
+      angular.extend($selectProvider.defaults, {
+          caretHtml: '&nbsp;<i class="icon icon-caret-down"></i>'
+      });
+        
+        // Initialize angular-translate
+        $translateProvider.useStaticFilesLoader({
+            prefix: './editor-app/i18n/',
+            suffix: '.json'
+        });
+
+        // $translateProvider.preferredLanguage('en');
+        $translateProvider.preferredLanguage('zh');
+
+        // remember language
+        $translateProvider.useCookieStorage();
+        
+  }])
+  .run(['$rootScope', '$timeout', '$modal', '$translate', '$location', '$window', '$http', '$q',
+        function($rootScope, $timeout, $modal, $translate, $location, $window, $http, $q) {
+	  
+			  $rootScope.config = ACTIVITI.CONFIG;
+			  
+			  $rootScope.editorInitialized = false;
+		      
+		      $rootScope.editorFactory = $q.defer();
+		
+		      $rootScope.forceSelectionRefresh = false;
+		
+		      $rootScope.ignoreChanges = false; // by default never ignore changes
+		      
+		      $rootScope.validationErrors = [];
+		      
+		      $rootScope.staticIncludeVersion = Date.now();
+
+			  /**
+		       * A 'safer' apply that avoids concurrent updates (which $apply allows).
+		       */
+		      $rootScope.safeApply = function(fn) {
+		          var phase = this.$root.$$phase;
+		          if(phase == '$apply' || phase == '$digest') {
+		              if(fn && (typeof(fn) === 'function')) {
+		                  fn();
+		              }
+		          } else {
+		              this.$apply(fn);
+		          }
+		      };
+	  
+	  
+            /**
+             * Initialize the event bus: couple all Oryx events with a dispatch of the
+             * event of the event bus. This way, it gets much easier to attach custom logic
+             * to any event.
+             */
+
+            /* Helper method to fetch model from server (always needed) */
+            function fetchModel(modelId) {
+
+                var modelUrl = KISBPM.URL.getModel(modelId);
+
+                $http({method: 'GET', url: modelUrl}).
+                    success(function (data, status, headers, config) {
+                        $rootScope.editor = new ORYX.Editor(data);
+                        $rootScope.modelData = angular.fromJson(data);
+                        $rootScope.editorFactory.resolve();
+                    }).
+                    error(function (data, status, headers, config) {
+                      console.log('Error loading model with id ' + modelId + ' ' + data);
+                    });
+            }
+
+
+            function initScrollHandling() {
+                var canvasSection = jQuery('#canvasSection');
+                canvasSection.scroll(function() {
+
+                    // Hides the resizer and quick menu items during scrolling
+
+                    var selectedElements = $rootScope.editor.selection;
+                    var subSelectionElements = $rootScope.editor._subSelection;
+
+                    $rootScope.selectedElements = selectedElements;
+                    $rootScope.subSelectionElements = subSelectionElements;
+                    if (selectedElements && selectedElements.length > 0) {
+                    	$rootScope.selectedElementBeforeScrolling = selectedElements[0];
+                    }
+
+                    jQuery('.Oryx_button').each(function(i, obj) {
+                    	$rootScope.orginalOryxButtonStyle = obj.style.display;
+                    	obj.style.display = 'none';
+                    });
+                    
+                    jQuery('.resizer_southeast').each(function(i, obj) {
+                    	$rootScope.orginalResizerSEStyle = obj.style.display;
+                        obj.style.display = 'none';
+                    });
+                    jQuery('.resizer_northwest').each(function(i, obj) {
+                    	$rootScope.orginalResizerNWStyle = obj.style.display;
+                        obj.style.display = 'none';
+                    });
+                    $rootScope.editor.handleEvents({type:ORYX.CONFIG.EVENT_CANVAS_SCROLL});
+                });
+
+                canvasSection.scrollStopped(function(){
+
+                    // Puts the quick menu items and resizer back when scroll is stopped.
+
+                    $rootScope.editor.setSelection([]); // needed cause it checks for element changes and does nothing if the elements are the same
+                    $rootScope.editor.setSelection($rootScope.selectedElements, $rootScope.subSelectionElements);
+                    $rootScope.selectedElements = undefined;
+                    $rootScope.subSelectionElements = undefined;
+
+                    function handleDisplayProperty(obj) {
+                        if (jQuery(obj).position().top > 0) {
+                            obj.style.display = 'block';
+                        } else {
+                            obj.style.display = 'none';
+                        }
+                    }
+
+                    jQuery('.Oryx_button').each(function(i, obj) {
+                        handleDisplayProperty(obj);
+                    });
+                    
+                    jQuery('.resizer_southeast').each(function(i, obj) {
+                        handleDisplayProperty(obj);
+                    });
+                    jQuery('.resizer_northwest').each(function(i, obj) {
+                        handleDisplayProperty(obj);
+                    });
+
+                });
+            }
+
+            /**
+             * Initialize the Oryx Editor when the content has been loaded
+             */
+            $rootScope.$on('$includeContentLoaded', function (event) {
+	            if (!$rootScope.editorInitialized) {
+
+	            	ORYX._loadPlugins();
+	
+	                var modelId = EDITOR.UTIL.getParameterByName('modelId');
+	                fetchModel(modelId);
+	
+	                $rootScope.window = {};
+	                var updateWindowSize = function() {
+	                    $rootScope.window.width = $window.innerWidth;
+	                    $rootScope.window.height  = $window.innerHeight;
+	                };
+	
+	                // Window resize hook
+	                angular.element($window).bind('resize', function() {
+	                    $rootScope.safeApply(updateWindowSize());
+	                });
+	
+	                $rootScope.$watch('window.forceRefresh', function(newValue) {
+	                    if(newValue) {
+	                        $timeout(function() {
+	                            updateWindowSize();
+	                            $rootScope.window.forceRefresh = false;
+	                        });
+	                    }
+	                });
+	
+	                updateWindowSize();
+
+	                // Hook in resizing of main panels when window resizes
+	                // TODO: perhaps move to a separate JS-file?
+	                jQuery(window).resize(function () {
+
+	                    // Calculate the offset based on the bottom of the module header
+	                    var offset = jQuery("#editor-header").offset();
+	                    var propSectionHeight = jQuery('#propertySection').height();
+	                    var canvas = jQuery('#canvasSection');
+	                    var mainHeader = jQuery('#main-header');
+
+	                    if (offset == undefined || offset === null
+	                        || propSectionHeight === undefined || propSectionHeight === null
+	                        || canvas === undefined || canvas === null || mainHeader === null) {
+	                        return;
+	                    }
+	                    
+	                    if ($rootScope.editor)
+	                	{
+	        	        	var selectedElements = $rootScope.editor.selection;
+	        	            var subSelectionElements = $rootScope.editor._subSelection;
+	        	
+	        	            $rootScope.selectedElements = selectedElements;
+	        	            $rootScope.subSelectionElements = subSelectionElements;
+	        	            if (selectedElements && selectedElements.length > 0)
+	        	            {
+	        	            	$rootScope.selectedElementBeforeScrolling = selectedElements[0];
+	        	            	
+	        	            	$rootScope.editor.setSelection([]); // needed cause it checks for element changes and does nothing if the elements are the same
+	        	                $rootScope.editor.setSelection($rootScope.selectedElements, $rootScope.subSelectionElements);
+	        	                $rootScope.selectedElements = undefined;
+	        	                $rootScope.subSelectionElements = undefined;
+	        	            }
+	                	}
+
+	                    var totalAvailable = jQuery(window).height() - offset.top - mainHeader.height() - 21;
+	                    canvas.height(totalAvailable - propSectionHeight);
+	                    jQuery('#paletteSection').height(totalAvailable);
+
+	                    // Update positions of the resize-markers, according to the canvas
+
+	                    var actualCanvas = null;
+	                    if (canvas && canvas[0].children[1]) {
+	                        actualCanvas = canvas[0].children[1];
+	                    }
+
+	                    var canvasTop = canvas.position().top;
+	                    var canvasLeft = canvas.position().left;
+	                    var canvasHeight = canvas[0].clientHeight;
+	                    var canvasWidth = canvas[0].clientWidth;
+	                    var iconCenterOffset = 8;
+	                    var widthDiff = 0;
+
+	                    var actualWidth = 0;
+	                    if(actualCanvas) {
+	                        // In some browsers, the SVG-element clientwidth isn't available, so we revert to the parent
+	                        actualWidth = actualCanvas.clientWidth || actualCanvas.parentNode.clientWidth;
+	                    }
+
+
+	                    if(actualWidth < canvas[0].clientWidth) {
+	                        widthDiff = actualWidth - canvas[0].clientWidth;
+	                        // In case the canvas is smaller than the actual viewport, the resizers should be moved
+	                        canvasLeft -= widthDiff / 2;
+	                        canvasWidth += widthDiff;
+	                    }
+
+	                    var iconWidth = 17;
+	                    var iconOffset = 20;
+
+	                    var north = jQuery('#canvas-grow-N');
+	                    north.css('top', canvasTop + iconOffset + 'px');
+	                    north.css('left', canvasLeft - 10 + (canvasWidth - iconWidth) / 2 + 'px');
+
+	                    var south = jQuery('#canvas-grow-S');
+	                    south.css('top', (canvasTop + canvasHeight - iconOffset - iconCenterOffset) +  'px');
+	                    south.css('left', canvasLeft - 10 + (canvasWidth - iconWidth) / 2 + 'px');
+
+	                    var east = jQuery('#canvas-grow-E');
+	                    east.css('top', canvasTop - 10 + (canvasHeight - iconWidth) / 2 + 'px');
+	                    east.css('left', (canvasLeft + canvasWidth - iconOffset - iconCenterOffset) + 'px');
+
+	                    var west = jQuery('#canvas-grow-W');
+	                    west.css('top', canvasTop -10 + (canvasHeight - iconWidth) / 2 + 'px');
+	                    west.css('left', canvasLeft + iconOffset + 'px');
+
+	                    north = jQuery('#canvas-shrink-N');
+	                    north.css('top', canvasTop + iconOffset + 'px');
+	                    north.css('left', canvasLeft + 10 + (canvasWidth - iconWidth) / 2 + 'px');
+
+	                    south = jQuery('#canvas-shrink-S');
+	                    south.css('top', (canvasTop + canvasHeight - iconOffset - iconCenterOffset) +  'px');
+	                    south.css('left', canvasLeft +10 + (canvasWidth - iconWidth) / 2 + 'px');
+
+	                    east = jQuery('#canvas-shrink-E');
+	                    east.css('top', canvasTop + 10 + (canvasHeight - iconWidth) / 2 +  'px');
+	                    east.css('left', (canvasLeft + canvasWidth - iconOffset - iconCenterOffset) + 'px');
+
+	                    west = jQuery('#canvas-shrink-W');
+	                    west.css('top', canvasTop + 10 + (canvasHeight - iconWidth) / 2 + 'px');
+	                    west.css('left', canvasLeft + iconOffset + 'px');
+	                });
+
+	                jQuery(window).trigger('resize');
+
+	                jQuery.fn.scrollStopped = function(callback) {
+	                    jQuery(this).scroll(function(){
+	                        var self = this, $this = jQuery(self);
+	                        if ($this.data('scrollTimeout')) {
+	                            clearTimeout($this.data('scrollTimeout'));
+	                        }
+	                        $this.data('scrollTimeout', setTimeout(callback,50,self));
+	                    });
+	                };
+	                
+	                // Always needed, cause the DOM element on which the scroll event listeners are attached are changed for every new model
+	                initScrollHandling();
+	                
+	                $rootScope.editorInitialized = true;
+	            }
+            });
+
+            /**
+             * Initialize the event bus: couple all Oryx events with a dispatch of the
+             * event of the event bus. This way, it gets much easier to attach custom logic
+             * to any event.
+             */
+
+            $rootScope.editorFactory.promise.then(function() {
+
+                KISBPM.eventBus.editor = $rootScope.editor;
+
+                var eventMappings = [
+                    { oryxType : ORYX.CONFIG.EVENT_SELECTION_CHANGED, kisBpmType : KISBPM.eventBus.EVENT_TYPE_SELECTION_CHANGE },
+                    { oryxType : ORYX.CONFIG.EVENT_DBLCLICK, kisBpmType : KISBPM.eventBus.EVENT_TYPE_DOUBLE_CLICK },
+                    { oryxType : ORYX.CONFIG.EVENT_MOUSEOUT, kisBpmType : KISBPM.eventBus.EVENT_TYPE_MOUSE_OUT },
+                    { oryxType : ORYX.CONFIG.EVENT_MOUSEOVER, kisBpmType : KISBPM.eventBus.EVENT_TYPE_MOUSE_OVER }
+
+                ];
+
+                eventMappings.forEach(function(eventMapping) {
+                    $rootScope.editor.registerOnEvent(eventMapping.oryxType, function(event) {
+                        KISBPM.eventBus.dispatch(eventMapping.kisBpmType, event);
+                    });
+                });
+                
+                $rootScope.editor.registerOnEvent(ORYX.CONFIG.EVENT_SHAPEREMOVED, function (event) {
+    	    		var validateButton = document.getElementById(event.shape.resourceId + "-validate-button");
+    	    		if (validateButton)
+    	    		{
+    	    			validateButton.style.display = 'none';
+    	    		}
+                });
+
+                // The Oryx canvas is ready (we know since we're in this promise callback) and the
+                // event bus is ready. The editor is now ready for use
+                KISBPM.eventBus.dispatch(KISBPM.eventBus.EVENT_TYPE_EDITOR_READY, {type : KISBPM.eventBus.EVENT_TYPE_EDITOR_READY});
+
+            });
+            
+            // Alerts
+            $rootScope.alerts = {
+                queue: []
+            };
+          
+            $rootScope.showAlert = function(alert) {
+                if(alert.queue.length > 0) {
+                    alert.current = alert.queue.shift();
+                    // Start timout for message-pruning
+                    alert.timeout = $timeout(function() {
+                        if (alert.queue.length == 0) {
+                            alert.current = undefined;
+                            alert.timeout = undefined;
+                        } else {
+                            $rootScope.showAlert(alert);
+                        }
+                    }, (alert.current.type == 'error' ? 5000 : 1000));
+                } else {
+                    $rootScope.alerts.current = undefined;
+                }
+            };
+          
+            $rootScope.addAlert = function(message, type) {
+                var newAlert = {message: message, type: type};
+                if (!$rootScope.alerts.timeout) {
+                    // Timeout for message queue is not running, start one
+                    $rootScope.alerts.queue.push(newAlert);
+                    $rootScope.showAlert($rootScope.alerts);
+                } else {
+                    $rootScope.alerts.queue.push(newAlert);
+                }
+            };
+          
+            $rootScope.dismissAlert = function() {
+                if (!$rootScope.alerts.timeout) {
+                    $rootScope.alerts.current = undefined;
+                } else {
+                    $timeout.cancel($rootScope.alerts.timeout);
+                    $rootScope.alerts.timeout = undefined;
+                    $rootScope.showAlert($rootScope.alerts);
+                }
+            };
+          
+            $rootScope.addAlertPromise = function(promise, type) {
+                if (promise) {
+                    promise.then(function(data) {
+                        $rootScope.addAlert(data, type);
+                    });
+                }
+            };
+          
+        }
+  ])
+
+    // Moment-JS date-formatting filter
+    .filter('dateformat', function() {
+        return function(date, format) {
+            if (date) {
+                if (format) {
+                    return moment(date).format(format);
+                } else {
+                    return moment(date).calendar();
+                }
+            }
+            return '';
+        };
+    });

+ 150 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-assignment-controller.js

@@ -0,0 +1,150 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Assignment
+ */
+var KisBpmAssignmentCtrl = [ '$scope', '$modal', function($scope, $modal) {
+
+    // Config for the modal window
+    var opts = {
+        template:  'editor-app/configuration/properties/assignment-popup.html?version=' + Date.now(),
+        scope: $scope
+    };
+
+    // Open the dialog
+    $modal(opts);
+}];
+
+var KisBpmAssignmentPopupCtrl = [ '$scope', function($scope) {
+    	
+    // Put json representing assignment on scope
+    if ($scope.property.value !== undefined && $scope.property.value !== null
+        && $scope.property.value.assignment !== undefined
+        && $scope.property.value.assignment !== null) 
+    {
+        $scope.assignment = $scope.property.value.assignment;
+    } else {
+        $scope.assignment = {};
+    }
+
+    if ($scope.assignment.candidateUsers == undefined || $scope.assignment.candidateUsers.length == 0)
+    {
+    	$scope.assignment.candidateUsers = [{value: ''}];
+    }
+    
+    // Click handler for + button after enum value
+    var userValueIndex = 1;
+    $scope.addCandidateUserValue = function(index) {
+        $scope.assignment.candidateUsers.splice(index + 1, 0, {value: 'value ' + userValueIndex++});
+    };
+
+    // Click handler for - button after enum value
+    $scope.removeCandidateUserValue = function(index) {
+        $scope.assignment.candidateUsers.splice(index, 1);
+    };
+    
+    if ($scope.assignment.candidateGroups == undefined || $scope.assignment.candidateGroups.length == 0)
+    {
+    	$scope.assignment.candidateGroups = [{value: ''}];
+    }
+    
+    var groupValueIndex = 1;
+    $scope.addCandidateGroupValue = function(index) {
+        $scope.assignment.candidateGroups.splice(index + 1, 0, {value: 'value ' + groupValueIndex++});
+    };
+
+    // Click handler for - button after enum value
+    $scope.removeCandidateGroupValue = function(index) {
+        $scope.assignment.candidateGroups.splice(index, 1);
+    };
+
+    $scope.save = function() {
+
+        $scope.property.value = {};
+        handleAssignmentInput($scope);
+        $scope.property.value.assignment = $scope.assignment;
+        
+        $scope.updatePropertyInModel($scope.property);
+        $scope.close();
+    };
+
+    // Close button handler
+    $scope.close = function() {
+    	handleAssignmentInput($scope);
+    	$scope.property.mode = 'read';
+    	$scope.$hide();
+    };
+    
+    var handleAssignmentInput = function($scope) {
+    	if ($scope.assignment.candidateUsers)
+    	{
+	    	var emptyUsers = true;
+	    	var toRemoveIndexes = [];
+	        for (var i = 0; i < $scope.assignment.candidateUsers.length; i++)
+	        {
+	        	if ($scope.assignment.candidateUsers[i].value != '')
+	        	{
+	        		emptyUsers = false;
+	        	}
+	        	else
+	        	{
+	        		toRemoveIndexes[toRemoveIndexes.length] = i;
+	        	}
+	        }
+	        
+	        for (var i = 0; i < toRemoveIndexes.length; i++)
+	        {
+	        	$scope.assignment.candidateUsers.splice(toRemoveIndexes[i], 1);
+	        }
+	        
+	        if (emptyUsers)
+	        {
+	        	$scope.assignment.candidateUsers = undefined;
+	        }
+    	}
+        
+    	if ($scope.assignment.candidateGroups)
+    	{
+	        var emptyGroups = true;
+	        var toRemoveIndexes = [];
+	        for (var i = 0; i < $scope.assignment.candidateGroups.length; i++)
+	        {
+	        	if ($scope.assignment.candidateGroups[i].value != '')
+	        	{
+	        		emptyGroups = false;
+	        	}
+	        	else
+	        	{
+	        		toRemoveIndexes[toRemoveIndexes.length] = i;
+	        	}
+	        }
+	        
+	        for (var i = 0; i < toRemoveIndexes.length; i++)
+	        {
+	        	$scope.assignment.candidateGroups.splice(toRemoveIndexes[i], 1);
+	        }
+	        
+	        if (emptyGroups)
+	        {
+	        	$scope.assignment.candidateGroups = undefined;
+	        }
+    	}
+    };
+}];

+ 58 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-condition-expression-controller.js

@@ -0,0 +1,58 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Condition expression
+ */
+
+var KisBpmConditionExpressionCtrl = [ '$scope', '$modal', function($scope, $modal) {
+
+    // Config for the modal window
+    var opts = {
+        template:  'editor-app/configuration/properties/condition-expression-popup.html?version=' + Date.now(),
+        scope: $scope
+    };
+
+    // Open the dialog
+    $modal(opts);
+}];
+
+var KisBpmConditionExpressionPopupCtrl = [ '$scope', '$translate', '$http', function($scope, $translate, $http) {
+
+	// Put json representing condition on scope
+    if ($scope.property.value !== undefined && $scope.property.value !== null) {
+
+        $scope.conditionExpression = {value: $scope.property.value};
+        
+    } else {
+        $scope.conditionExpression = {value: ''};
+    }
+	
+    $scope.save = function() {
+        $scope.property.value = $scope.conditionExpression.value;
+        $scope.updatePropertyInModel($scope.property);
+        $scope.close();
+    };
+
+    // Close button handler
+    $scope.close = function() {
+    	$scope.property.mode = 'read';
+    	$scope.$hide();
+    };
+}];

+ 18 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-custom-controllers.js

@@ -0,0 +1,18 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */

+ 115 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-default-controllers.js

@@ -0,0 +1,115 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * String controller
+ */
+
+var KisBpmStringPropertyCtrl = [ '$scope', function ($scope) {
+
+	$scope.shapeId = $scope.selectedShape.id;
+	$scope.valueFlushed = false;
+    /** Handler called when input field is blurred */
+    $scope.inputBlurred = function() {
+    	$scope.valueFlushed = true;
+    	if ($scope.property.value) {
+    		$scope.property.value = $scope.property.value.replace(/(<([^>]+)>)/ig,"");
+    	}
+        $scope.updatePropertyInModel($scope.property);
+    };
+
+    $scope.enterPressed = function(keyEvent) {
+    	if (keyEvent && keyEvent.which === 13) {
+    		keyEvent.preventDefault();
+	        $scope.inputBlurred(); // we want to do the same as if the user would blur the input field
+    	}
+    };
+    
+    $scope.$on('$destroy', function controllerDestroyed() {
+    	if(!$scope.valueFlushed) {
+    		if ($scope.property.value) {
+        		$scope.property.value = $scope.property.value.replace(/(<([^>]+)>)/ig,"");
+        	}
+    		$scope.updatePropertyInModel($scope.property, $scope.shapeId);
+    	}
+    });
+
+}];
+
+/*
+ * Boolean controller
+ */
+
+var KisBpmBooleanPropertyCtrl = ['$scope', function ($scope) {
+
+    $scope.changeValue = function() {
+        if ($scope.property.key === 'oryx-defaultflow' && $scope.property.value) {
+            var selectedShape = $scope.selectedShape;
+            if (selectedShape) {
+                var incomingNodes = selectedShape.getIncomingShapes();
+                if (incomingNodes && incomingNodes.length > 0) {
+                    // get first node, since there can be only one for a sequence flow
+                    var rootNode = incomingNodes[0];
+                    var flows = rootNode.getOutgoingShapes();
+                    if (flows && flows.length > 1) {
+                        // in case there are more flows, check if another flow is already defined as default
+                        for (var i = 0; i < flows.length; i++) {
+                            if (flows[i].resourceId != selectedShape.resourceId) {
+                                var defaultFlowProp = flows[i].properties['oryx-defaultflow'];
+                                if (defaultFlowProp) {
+                                    flows[i].setProperty('oryx-defaultflow', false, true);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        $scope.updatePropertyInModel($scope.property);
+    };
+
+}];
+
+/*
+ * Text controller
+ */
+
+var KisBpmTextPropertyCtrl = [ '$scope', '$modal', function($scope, $modal) {
+
+    var opts = {
+        template:  'editor-app/configuration/properties/text-popup.html?version=' + Date.now(),
+        scope: $scope
+    };
+
+    // Open the dialog
+    $modal(opts);
+}];
+
+var KisBpmTextPropertyPopupCtrl = ['$scope', function($scope) {
+    
+    $scope.save = function() {
+        $scope.updatePropertyInModel($scope.property);
+        $scope.close();
+    };
+
+    $scope.close = function() {
+        $scope.property.mode = 'read';
+        $scope.$hide();
+    };
+}];

+ 266 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-event-listeners-controller.js

@@ -0,0 +1,266 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Execution listeners
+ */
+
+var KisBpmEventListenersCtrl = [ '$scope', '$modal', '$timeout', '$translate', function($scope, $modal, $timeout, $translate) {
+
+    // Config for the modal window
+    var opts = {
+        template:  'editor-app/configuration/properties/event-listeners-popup.html?version=' + Date.now(),
+        scope: $scope
+    };
+
+    // Open the dialog
+    $modal(opts);
+}];
+
+//Need a separate controller for the modal window due to https://github.com/angular-ui/bootstrap/issues/259
+// Will be fixed in a newer version of Angular UI
+var KisBpmEventListenersPopupCtrl = [ '$scope', '$q', '$translate', function($scope, $q, $translate) {
+
+    // Put json representing form properties on scope
+    if ($scope.property.value !== undefined && $scope.property.value !== null
+        && $scope.property.value.eventListeners !== undefined
+        && $scope.property.value.eventListeners !== null) {
+    	
+    	if ($scope.property.value.eventListeners.constructor == String)
+    	{
+    		$scope.eventListeners = JSON.parse($scope.property.value.eventListeners);
+    	}
+    	else
+    	{
+    		// Note that we clone the json object rather then setting it directly,
+            // this to cope with the fact that the user can click the cancel button and no changes should have happened
+    		$scope.eventListeners = angular.copy($scope.property.value.eventListeners);
+    	}
+    	
+    } else {
+        $scope.eventListeners = [];
+    }
+
+    // Array to contain selected properties (yes - we only can select one, but ng-grid isn't smart enough)
+    $scope.selectedListeners = [];
+    $scope.translationsRetrieved = false;
+    
+    $scope.labels = {};
+    
+    var eventPromise = $translate('PROPERTY.EXECUTIONLISTENERS.EVENT');
+    var implementationPromise = $translate('PROPERTY.EXECUTIONLISTENERS.FIELDS.IMPLEMENTATION');
+    var namePromise = $translate('PROPERTY.EXECUTIONLISTENERS.FIELDS.NAME');
+    
+    $q.all([eventPromise, implementationPromise, namePromise]).then(function(results) { 
+        $scope.labels.eventLabel = results[0];
+        $scope.labels.implementationLabel = results[1];
+        $scope.labels.nameLabel = results[2];
+        $scope.translationsRetrieved = true;
+        
+        // Config for grid
+        $scope.gridOptions = {
+            data: 'eventListeners',
+            enableRowReordering: true,
+            headerRowHeight: 28,
+            multiSelect: false,
+            keepLastSelected : false,
+            selectedItems: $scope.selectedListeners,
+            afterSelectionChange: function (rowItem, event) {
+            	
+            	if ($scope.selectedListeners.length > 0)
+            	{
+	            	var fields = $scope.selectedListeners[0].fields;
+	            	if (fields !== undefined && fields !== null)
+	            	{
+	            		for (var i = 0; i < fields.length; i++)
+	            		{
+	            			var field = fields[i];
+	            			if (field.stringValue !== undefined && field.stringValue !== '')
+	                    	{
+	            				field.implementation = field.stringValue;
+	                    	}
+	                    	else if (field.expression !== undefined && field.expression !== '')
+	                    	{
+	                    		field.implementation = field.expression;
+	                    	}
+	                    	else if (field.string !== undefined && field.string !== '')
+	                    	{
+	                    		field.implementation = field.string;
+	                    	}
+	            		}
+	            	}
+	            	
+	            	if (!$scope.selectedListeners[0].events || $scope.selectedListeners[0].events.length == 0)
+	                {
+	                	$scope.selectedListeners[0].events = [{event: ''}];
+	                }
+            	}
+            },
+            columnDefs: [{ field: 'event', displayName: $scope.labels.eventLabel },
+                { field: 'implementation', displayName: $scope.labels.implementationLabel }]
+        };
+    });
+    
+    // Click handler for + button after enum value
+    $scope.addEventValue = function(index) {
+        $scope.selectedListeners[0].events.splice(index + 1, 0, {event: ''});
+    };
+
+    // Click handler for - button after enum value
+    $scope.removeEventValue = function(index) {
+        $scope.selectedListeners[0].events.splice(index, 1);
+        $scope.listenerDetailsChanged();
+    };
+    
+    $scope.listenerDetailsChanged = function() {
+    	var listener = $scope.selectedListeners[0];
+    	if (listener.events)
+    	{
+    		var eventText = '';
+    		for (var i = 0; i < listener.events.length; i++)
+    		{
+    			if (i > 0)
+    			{
+    				eventText += ", ";
+    			}
+    			eventText += listener.events[i].event;
+    		}
+    		$scope.selectedListeners[0].event = eventText;
+    	}
+    	
+    	if (listener.rethrowEvent)
+    	{
+    		var implementationText = '';
+    		if (listener.rethrowType && listener.rethrowType.length > 0)
+    		{
+    			if (listener.rethrowType === 'error' && listener.errorcode !== '')
+	        	{
+	        		implementationText = "Rethrow as error " + listener.errorcode;
+	        	}
+    			else if (listener.rethrowType === 'message' && listener.messagename !== '')
+	        	{
+	        		implementationText = "Rethrow as message " + listener.messagename;
+	        	}
+    			else if ((listener.rethrowType === 'signal' || listener.rethrowType === 'globalSignal') && listener.signalname !== '')
+	        	{
+	        		implementationText = "Rethrow as signal " + listener.signalname;
+	        	}
+    		}
+    		$scope.selectedListeners[0].implementation = implementationText;
+    	}
+    	else
+    	{
+        	if ($scope.selectedListeners[0].className !== '')
+        	{
+        		$scope.selectedListeners[0].implementation = $scope.selectedListeners[0].className;
+        	}
+        	else if ($scope.selectedListeners[0].delegateExpression !== '')
+        	{
+        		$scope.selectedListeners[0].implementation = $scope.selectedListeners[0].delegateExpression;
+        	}
+        	else
+        	{
+        		$scope.selectedListeners[0].implementation = '';
+        	}
+    	}
+    };
+
+    // Click handler for add button
+    $scope.addNewListener = function() {
+        $scope.eventListeners.push({ event : '',
+            implementation : '',
+            className : '',
+            delegateExpression: '',
+            retrowEvent: false});
+    };
+
+    // Click handler for remove button
+    $scope.removeListener = function() {
+        if ($scope.selectedListeners.length > 0) {
+            var index = $scope.eventListeners.indexOf($scope.selectedListeners[0]);
+            $scope.gridOptions.selectItem(index, false);
+            $scope.eventListeners.splice(index, 1);
+
+            $scope.selectedListeners.length = 0;
+            if (index < $scope.eventListeners.length) {
+                $scope.gridOptions.selectItem(index + 1, true);
+            } else if ($scope.eventListeners.length > 0) {
+                $scope.gridOptions.selectItem(index - 1, true);
+            }
+        }
+    };
+
+    // Click handler for up button
+    $scope.moveListenerUp = function() {
+        if ($scope.selectedListeners.length > 0) {
+            var index = $scope.eventListeners.indexOf($scope.selectedListeners[0]);
+            if (index != 0) { // If it's the first, no moving up of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.eventListeners[index];
+                $scope.eventListeners.splice(index, 1);
+                $timeout(function(){
+                    $scope.eventListeners.splice(index + -1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for down button
+    $scope.moveListenerDown = function() {
+        if ($scope.selectedListeners.length > 0) {
+            var index = $scope.eventListeners.indexOf($scope.selectedListeners[0]);
+            if (index != $scope.eventListeners.length - 1) { // If it's the last element, no moving down of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.eventListeners[index];
+                $scope.eventListeners.splice(index, 1);
+                $timeout(function(){
+                    $scope.eventListeners.splice(index + 1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for save button
+    $scope.save = function() {
+
+        if ($scope.eventListeners.length > 0) {
+            $scope.property.value = {};
+            $scope.property.value.eventListeners = $scope.eventListeners;
+        } else {
+            $scope.property.value = null;
+        }
+
+        $scope.updatePropertyInModel($scope.property);
+        $scope.close();
+    };
+
+    $scope.cancel = function() {
+    	$scope.property.mode = 'read';
+    	$scope.$hide();
+    };
+
+    // Close button handler
+    $scope.close = function() {
+    	$scope.property.mode = 'read';
+    	$scope.$hide();
+    };
+
+}];

+ 326 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-execution-listeners-controller.js

@@ -0,0 +1,326 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Execution listeners
+ */
+
+var KisBpmExecutionListenersCtrl = [ '$scope', '$modal', '$timeout', '$translate', function($scope, $modal, $timeout, $translate) {
+
+    // Config for the modal window
+    var opts = {
+        template:  'editor-app/configuration/properties/execution-listeners-popup.html?version=' + Date.now(),
+        scope: $scope
+    };
+
+    // Open the dialog
+    $modal(opts);
+}];
+
+var KisBpmExecutionListenersPopupCtrl = [ '$scope', '$q', '$translate', function($scope, $q, $translate) {
+
+    // Put json representing form properties on scope
+    if ($scope.property.value !== undefined && $scope.property.value !== null
+        && $scope.property.value.executionListeners !== undefined
+        && $scope.property.value.executionListeners !== null) {
+        
+        if ($scope.property.value.executionListeners.constructor == String)
+        {
+            $scope.executionListeners = JSON.parse($scope.property.value.executionListeners);
+        }
+        else
+        {
+            // Note that we clone the json object rather then setting it directly,
+            // this to cope with the fact that the user can click the cancel button and no changes should have happened
+            $scope.executionListeners = angular.copy($scope.property.value.executionListeners);
+        }
+        
+        for (var i = 0; i < $scope.executionListeners.length; i++)
+        {
+            var executionListener = $scope.executionListeners[i];
+            if (executionListener.className !== undefined && executionListener.className !== '')
+            {
+                executionListener.implementation = executionListener.className;
+            }
+            else if (executionListener.expression !== undefined && executionListener.expression !== '')
+            {
+                executionListener.implementation = executionListener.expression;
+            }
+            else if (executionListener.delegateExpression !== undefined && executionListener.delegateExpression !== '')
+            {
+                executionListener.implementation = executionListener.delegateExpression;
+            }
+        }
+    } else {
+        $scope.executionListeners = [];
+    }
+
+    // Array to contain selected properties (yes - we only can select one, but ng-grid isn't smart enough)
+    $scope.selectedListeners = [];
+    $scope.selectedFields = [];
+    $scope.translationsRetrieved = false;
+    
+    $scope.labels = {};
+    
+    var eventPromise = $translate('PROPERTY.EXECUTIONLISTENERS.EVENT');
+    var implementationPromise = $translate('PROPERTY.EXECUTIONLISTENERS.FIELDS.IMPLEMENTATION');
+    var namePromise = $translate('PROPERTY.EXECUTIONLISTENERS.FIELDS.NAME');
+    
+    $q.all([eventPromise, implementationPromise, namePromise]).then(function(results) { 
+        $scope.labels.eventLabel = results[0];
+        $scope.labels.implementationLabel = results[1];
+        $scope.labels.nameLabel = results[2];
+        $scope.translationsRetrieved = true;
+        
+        // Config for grid
+        $scope.gridOptions = {
+            data: 'executionListeners',
+            enableRowReordering: true,
+            headerRowHeight: 28,
+            multiSelect: false,
+            keepLastSelected : false,
+            selectedItems: $scope.selectedListeners,
+            afterSelectionChange: function (rowItem, event) {
+                $scope.selectedFields.length = 0;
+                if ($scope.selectedListeners.length > 0)
+                {
+                    var fields = $scope.selectedListeners[0].fields;
+                    if (fields !== undefined && fields !== null)
+                    {
+                        for (var i = 0; i < fields.length; i++)
+                        {
+                            var field = fields[i];
+                            if (field.stringValue !== undefined && field.stringValue !== '')
+                            {
+                                field.implementation = field.stringValue;
+                            }
+                            else if (field.expression !== undefined && field.expression !== '')
+                            {
+                                field.implementation = field.expression;
+                            }
+                            else if (field.string !== undefined && field.string !== '')
+                            {
+                                field.implementation = field.string;
+                            }
+                        }
+                    }
+                }
+            },
+            columnDefs: [{ field: 'event', displayName: $scope.labels.eventLabel },
+                { field: 'implementation', displayName: $scope.labels.implementationLabel }]
+        };
+        
+        // Config for field grid
+        $scope.gridFieldOptions = {
+            data: 'selectedListeners[0].fields',
+            enableRowReordering: true,
+            headerRowHeight: 28,
+            multiSelect: false,
+            keepLastSelected : false,
+            selectedItems: $scope.selectedFields,
+            columnDefs: [{ field: 'name', displayName: $scope.labels.nameLabel },
+                { field: 'implementation', displayName: $scope.labels.implementationLabel}]
+        };
+    });
+    
+    $scope.listenerDetailsChanged = function() {
+        if ($scope.selectedListeners[0].className !== '')
+        {
+            $scope.selectedListeners[0].implementation = $scope.selectedListeners[0].className;
+        }
+        else if ($scope.selectedListeners[0].expression !== '')
+        {
+            $scope.selectedListeners[0].implementation = $scope.selectedListeners[0].expression;
+        }
+        else if ($scope.selectedListeners[0].delegateExpression !== '')
+        {
+            $scope.selectedListeners[0].implementation = $scope.selectedListeners[0].delegateExpression;
+        }
+        else
+        {
+            $scope.selectedListeners[0].implementation = '';
+        }
+    };
+
+    // Click handler for add button
+    $scope.addNewListener = function() {
+        $scope.executionListeners.push({ event : 'start',
+            implementation : '',
+            className : '',
+            expression: '',
+            delegateExpression: ''});
+    };
+
+    // Click handler for remove button
+    $scope.removeListener = function() {
+        if ($scope.selectedListeners.length > 0) {
+            var index = $scope.executionListeners.indexOf($scope.selectedListeners[0]);
+            $scope.gridOptions.selectItem(index, false);
+            $scope.executionListeners.splice(index, 1);
+
+            $scope.selectedListeners.length = 0;
+            if (index < $scope.executionListeners.length) {
+                $scope.gridOptions.selectItem(index + 1, true);
+            } else if ($scope.executionListeners.length > 0) {
+                $scope.gridOptions.selectItem(index - 1, true);
+            }
+        }
+    };
+
+    // Click handler for up button
+    $scope.moveListenerUp = function() {
+        if ($scope.selectedListeners.length > 0) {
+            var index = $scope.executionListeners.indexOf($scope.selectedListeners[0]);
+            if (index != 0) { // If it's the first, no moving up of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.executionListeners[index];
+                $scope.executionListeners.splice(index, 1);
+                $timeout(function(){
+                    $scope.executionListeners.splice(index + -1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for down button
+    $scope.moveListenerDown = function() {
+        if ($scope.selectedListeners.length > 0) {
+            var index = $scope.executionListeners.indexOf($scope.selectedListeners[0]);
+            if (index != $scope.executionListeners.length - 1) { // If it's the last element, no moving down of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.executionListeners[index];
+                $scope.executionListeners.splice(index, 1);
+                $timeout(function(){
+                    $scope.executionListeners.splice(index + 1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+    
+    $scope.fieldDetailsChanged = function() {
+        if ($scope.selectedFields[0].stringValue !== '')
+        {
+            $scope.selectedFields[0].implementation = $scope.selectedFields[0].stringValue;
+        }
+        else if ($scope.selectedFields[0].expression !== '')
+        {
+            $scope.selectedFields[0].implementation = $scope.selectedFields[0].expression;
+        }
+        else if ($scope.selectedFields[0].string !== '')
+        {
+            $scope.selectedFields[0].implementation = $scope.selectedFields[0].string;
+        }
+        else
+        {
+            $scope.selectedFields[0].implementation = '';
+        }
+    };
+
+    // Click handler for add button
+    $scope.addNewField = function() {
+        if ($scope.selectedListeners.length > 0)
+        {
+            if ($scope.selectedListeners[0].fields == undefined)
+            {
+                $scope.selectedListeners[0].fields = [];
+            }
+            $scope.selectedListeners[0].fields.push({ name : 'fieldName',
+                implementation : '',
+                stringValue : '',
+                expression: '',
+                string: ''});
+        }
+    };
+
+    // Click handler for remove button
+    $scope.removeField = function() {
+        if ($scope.selectedFields.length > 0) {
+            var index = $scope.selectedListeners[0].fields.indexOf($scope.selectedFields[0]);
+            $scope.gridFieldOptions.selectItem(index, false);
+            $scope.selectedListeners[0].fields.splice(index, 1);
+
+            $scope.selectedFields.length = 0;
+            if (index < $scope.selectedListeners[0].fields.length) {
+                $scope.gridFieldOptions.selectItem(index + 1, true);
+            } else if ($scope.selectedListeners[0].fields.length > 0) {
+                $scope.gridFieldOptions.selectItem(index - 1, true);
+            }
+        }
+    };
+
+    // Click handler for up button
+    $scope.moveFieldUp = function() {
+        if ($scope.selectedFields.length > 0) {
+            var index = $scope.selectedListeners[0].fields.indexOf($scope.selectedFields[0]);
+            if (index != 0) { // If it's the first, no moving up of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.selectedListeners[0].fields[index];
+                $scope.selectedListeners[0].fields.splice(index, 1);
+                $timeout(function(){
+                    $scope.selectedListeners[0].fields.splice(index + -1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for down button
+    $scope.moveFieldDown = function() {
+        if ($scope.selectedFields.length > 0) {
+            var index = $scope.selectedListeners[0].fields.indexOf($scope.selectedFields[0]);
+            if (index != $scope.selectedListeners[0].fields.length - 1) { // If it's the last element, no moving down of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.selectedListeners[0].fields[index];
+                $scope.selectedListeners[0].fields.splice(index, 1);
+                $timeout(function(){
+                    $scope.selectedListeners[0].fields.splice(index + 1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for save button
+    $scope.save = function() {
+
+        if ($scope.executionListeners.length > 0) {
+            $scope.property.value = {};
+            $scope.property.value.executionListeners = $scope.executionListeners;
+        } else {
+            $scope.property.value = null;
+        }
+
+        $scope.updatePropertyInModel($scope.property);
+        $scope.close();
+    };
+
+    $scope.cancel = function() {
+        $scope.$hide();
+        $scope.property.mode = 'read';
+    };
+
+    // Close button handler
+    $scope.close = function() {
+        $scope.$hide();
+        $scope.property.mode = 'read';
+    };
+
+}];

+ 192 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-fields-controller.js

@@ -0,0 +1,192 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Task listeners
+ */
+
+var KisBpmFieldsCtrl = [ '$scope', '$modal', '$timeout', '$translate', function($scope, $modal, $timeout, $translate) {
+
+    // Config for the modal window
+    var opts = {
+        template:  'editor-app/configuration/properties/fields-popup.html',
+        scope: $scope
+    };
+
+    // Open the dialog
+    $modal(opts);
+}];
+
+var KisBpmFieldsPopupCtrl = [ '$scope', '$q', '$translate', function($scope, $q, $translate) {
+
+    // Put json representing form properties on scope
+    if ($scope.property.value !== undefined && $scope.property.value !== null
+        && $scope.property.value.fields !== undefined
+        && $scope.property.value.fields !== null) {
+        // Note that we clone the json object rather then setting it directly,
+        // this to cope with the fact that the user can click the cancel button and no changes should have happened
+        $scope.fields = angular.copy($scope.property.value.fields);
+        
+        for (var i = 0; i < $scope.fields.length; i++)
+		{
+			var field = $scope.fields[i];
+			if (field.stringValue !== undefined && field.stringValue !== '')
+        	{
+				field.implementation = field.stringValue;
+        	}
+        	else if (field.expression !== undefined && field.expression !== '')
+        	{
+        		field.implementation = field.expression;
+        	}
+        	else if (field.string !== undefined && field.string !== '')
+        	{
+        		field.implementation = field.string;
+        	}
+		}
+        
+    } else {
+        $scope.fields = [];
+    }
+
+    // Array to contain selected properties (yes - we only can select one, but ng-grid isn't smart enough)
+    $scope.selectedFields = [];
+    $scope.translationsRetrieved = false;
+    $scope.labels = {};
+
+    var namePromise = $translate('PROPERTY.FIELDS.NAME');
+    var implementationPromise = $translate('PROPERTY.FIELDS.IMPLEMENTATION');
+
+    $q.all([namePromise, implementationPromise]).then(function(results) {
+        $scope.labels.nameLabel = results[0];
+        $scope.labels.implementationLabel = results[1];
+        $scope.translationsRetrieved = true;
+
+        // Config for grid
+        $scope.gridOptions = {
+            data: 'fields',
+            enableRowReordering: true,
+            headerRowHeight: 28,
+            multiSelect: false,
+            keepLastSelected: false,
+            selectedItems: $scope.selectedFields,
+            columnDefs: [{field: 'name', displayName: $scope.labels.nameLabel},
+                {field: 'implementation', displayName: $scope.labels.implementationLabel}]
+        };
+    });
+    
+    $scope.fieldDetailsChanged = function() {
+    	if ($scope.selectedFields[0].stringValue != '')
+    	{
+    		$scope.selectedFields[0].implementation = $scope.selectedFields[0].stringValue;
+    	}
+    	else if ($scope.selectedFields[0].expression != '')
+    	{
+    		$scope.selectedFields[0].implementation = $scope.selectedFields[0].expression;
+    	}
+    	else if ($scope.selectedFields[0].string != '')
+    	{
+    		$scope.selectedFields[0].implementation = $scope.selectedFields[0].string;
+    	}
+    	else
+    	{
+    		$scope.selectedFields[0].implementation = '';
+    	}
+    };
+
+    // Click handler for add button
+    $scope.addNewField = function() {
+    	$scope.fields.push({ name : 'fieldName',
+            implementation : '',
+            stringValue : '',
+            expression: '',
+            string: ''});
+    };
+
+    // Click handler for remove button
+    $scope.removeField = function() {
+        if ($scope.selectedFields.length > 0) {
+            var index = $scope.fields.indexOf($scope.selectedFields[0]);
+            $scope.gridOptions.selectItem(index, false);
+            $scope.fields.splice(index, 1);
+
+            $scope.selectedFields.length = 0;
+            if (index < $scope.fields.length) {
+                $scope.gridOptions.selectItem(index + 1, true);
+            } else if ($scope.fields.length > 0) {
+                $scope.gridOptions.selectItem(index - 1, true);
+            }
+        }
+    };
+
+    // Click handler for up button
+    $scope.moveFieldUp = function() {
+        if ($scope.selectedFields.length > 0) {
+            var index = $scope.fields.indexOf($scope.selectedFields[0]);
+            if (index != 0) { // If it's the first, no moving up of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.fields[index];
+                $scope.fields.splice(index, 1);
+                $timeout(function(){
+                	$scope.fields.splice(index + -1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for down button
+    $scope.moveFieldDown = function() {
+        if ($scope.selectedFields.length > 0) {
+            var index = $scope.fields.indexOf($scope.selectedFields[0]);
+            if (index != $scope.fields.length - 1) { // If it's the last element, no moving down of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.fields[index];
+                $scope.fields.splice(index, 1);
+                $timeout(function(){
+                	$scope.fields.splice(index + 1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for save button
+    $scope.save = function() {
+
+        if ($scope.fields.length > 0) {
+            $scope.property.value = {};
+            $scope.property.value.fields = $scope.fields;
+        } else {
+            $scope.property.value = null;
+        }
+
+        $scope.updatePropertyInModel($scope.property);
+        $scope.close();
+    };
+
+    $scope.cancel = function() {
+        $scope.close();
+    };
+
+    // Close button handler
+    $scope.close = function() {
+        $scope.property.mode = 'read';
+        $scope.$hide();
+    };
+}];

+ 276 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-form-properties-controller.js

@@ -0,0 +1,276 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Form Properties
+ */
+
+var KisBpmFormPropertiesCtrl = [ '$scope', '$modal', '$timeout', '$translate', function($scope, $modal, $timeout, $translate) {
+
+    // Config for the modal window
+    var opts = {
+        template:  'editor-app/configuration/properties/form-properties-popup.html?version=' + Date.now(),
+        scope: $scope
+    };
+
+    // Open the dialog
+    $modal(opts);
+}];
+
+var KisBpmFormPropertiesPopupCtrl = ['$scope', '$q', '$translate', '$timeout', function($scope, $q, $translate, $timeout) {
+
+    // Put json representing form properties on scope
+    if ($scope.property.value !== undefined && $scope.property.value !== null
+        && $scope.property.value.formProperties !== undefined
+        && $scope.property.value.formProperties !== null) {
+        // Note that we clone the json object rather then setting it directly,
+        // this to cope with the fact that the user can click the cancel button and no changes should have happended
+        $scope.formProperties = angular.copy($scope.property.value.formProperties);
+        
+        for (var i = 0; i < $scope.formProperties.length; i++) {
+        	var formProperty = $scope.formProperties[i];
+        	if (formProperty.enumValues && formProperty.enumValues.length > 0) {
+        		for (var j = 0; j < formProperty.enumValues.length; j++) {
+        			var enumValue = formProperty.enumValues[j];
+        			if (!enumValue.id && !enumValue.name && enumValue.value) {
+        				enumValue.id = enumValue.value;
+        				enumValue.name = enumValue.value;
+        			}
+        		}
+        	}
+        }
+        
+    } else {
+        $scope.formProperties = [];
+    }
+
+    // Array to contain selected properties (yes - we only can select one, but ng-grid isn't smart enough)
+    $scope.selectedProperties = [];
+    $scope.selectedEnumValues = [];
+    
+    $scope.translationsRetrieved = false;
+    
+    $scope.labels = {};
+    
+    var idPromise = $translate('PROPERTY.FORMPROPERTIES.ID');
+    var namePromise = $translate('PROPERTY.FORMPROPERTIES.NAME');
+    var typePromise = $translate('PROPERTY.FORMPROPERTIES.TYPE');
+    
+    $q.all([idPromise, namePromise, typePromise]).then(function(results) { 
+    	$scope.labels.idLabel = results[0];
+        $scope.labels.nameLabel = results[1];
+        $scope.labels.typeLabel = results[2];
+        $scope.translationsRetrieved = true;
+        
+    	// Config for grid
+        $scope.gridOptions = {
+            data: 'formProperties',
+            enableRowReordering: true,
+            headerRowHeight: 28,
+            multiSelect: false,
+            keepLastSelected : false,
+            selectedItems: $scope.selectedProperties,
+            columnDefs: [{ field: 'id', displayName: $scope.labels.idLabel },
+                { field: 'name', displayName: $scope.labels.nameLabel},
+                { field: 'type', displayName: $scope.labels.typeLabel}]
+        };
+        
+        $scope.enumGridOptions = {
+    		data: 'selectedProperties[0].enumValues',
+            enableRowReordering: true,
+            headerRowHeight: 28,
+            multiSelect: false,
+            keepLastSelected : false,
+            selectedItems: $scope.selectedEnumValues,
+            columnDefs: [{ field: 'id', displayName: $scope.labels.idLabel },
+                { field: 'name', displayName: $scope.labels.nameLabel}]
+        }
+    });
+
+    // Handler for when the value of the type dropdown changes
+    $scope.propertyTypeChanged = function() {
+
+        // Check date. If date, show date pattern
+        if ($scope.selectedProperties[0].type === 'date') {
+            $scope.selectedProperties[0].datePattern = 'MM-dd-yyyy hh:mm';
+            
+        } else {
+            delete $scope.selectedProperties[0].datePattern;
+        }
+
+        // Check enum. If enum, show list of options
+        if ($scope.selectedProperties[0].type === 'enum') {
+            $scope.selectedProperties[0].enumValues = [ {id: 'value1', name: 'Value 1'}, {id: 'value2', name: 'Value 2'}];
+            
+        } else {
+            delete $scope.selectedProperties[0].enumValues;
+        }
+    };
+
+    // Click handler for add button
+    var propertyIndex = 1;
+    $scope.addNewProperty = function() {
+        $scope.formProperties.push({ id : 'new_property_' + propertyIndex++,
+            name : '',
+            type : 'string',
+            readable: true,
+            writable: true});
+        
+        $timeout(function(){
+        	$scope.gridOptions.selectItem($scope.formProperties.length - 1, true);
+        });
+    };
+
+    // Click handler for remove button
+    $scope.removeProperty = function() {
+        if ($scope.selectedProperties.length > 0) {
+            var index = $scope.formProperties.indexOf($scope.selectedProperties[0]);
+            $scope.gridOptions.selectItem(index, false);
+            $scope.formProperties.splice(index, 1);
+
+            $scope.selectedProperties.length = 0;
+            if (index < $scope.formProperties.length) {
+                $scope.gridOptions.selectItem(index + 1, true);
+            } else if ($scope.formProperties.length > 0) {
+                $scope.gridOptions.selectItem(index - 1, true);
+            }
+        }
+    };
+
+    // Click handler for up button
+    $scope.movePropertyUp = function() {
+        if ($scope.selectedProperties.length > 0) {
+            var index = $scope.formProperties.indexOf($scope.selectedProperties[0]);
+            if (index != 0) { // If it's the first, no moving up of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.formProperties[index];
+                $scope.formProperties.splice(index, 1);
+                $timeout(function(){
+                    $scope.formProperties.splice(index + -1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for down button
+    $scope.movePropertyDown = function() {
+        if ($scope.selectedProperties.length > 0) {
+            var index = $scope.formProperties.indexOf($scope.selectedProperties[0]);
+            if (index != $scope.formProperties.length - 1) { // If it's the last element, no moving down of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.formProperties[index];
+                $scope.formProperties.splice(index, 1);
+                $timeout(function(){
+                    $scope.formProperties.splice(index + 1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+    
+    $scope.addNewEnumValue = function() {
+    	if ($scope.selectedProperties.length > 0) {
+	        $scope.selectedProperties[0].enumValues.push({ id : '', name : ''});
+    	}
+    	
+    	$timeout(function(){
+        	$scope.enumGridOptions.selectItem($scope.selectedProperties[0].enumValues.length - 1, true);
+        });
+    };
+
+    // Click handler for remove button
+    $scope.removeEnumValue = function() {
+    	if ($scope.selectedProperties.length > 0 && $scope.selectedEnumValues.length > 0) {
+            var index = $scope.selectedProperties[0].enumValues.indexOf($scope.selectedEnumValues[0]);
+            $scope.enumGridOptions.selectItem(index, false);
+            $scope.selectedProperties[0].enumValues.splice(index, 1);
+
+            $scope.selectedEnumValues.length = 0;
+            if (index < $scope.selectedProperties[0].enumValues.length) {
+            	$timeout(function(){
+            		$scope.enumGridOptions.selectItem(index + 1, true);
+            	});
+            	
+            } else if ($scope.selectedProperties[0].enumValues.length > 0) {
+            	$timeout(function(){
+            		$scope.enumGridOptions.selectItem(index - 1, true);
+            	});
+            }
+        }
+    };
+
+    // Click handler for up button
+    $scope.moveEnumValueUp = function() {
+    	if ($scope.selectedProperties.length > 0 && $scope.selectedEnumValues.length > 0) {
+    		var index = $scope.selectedProperties[0].enumValues.indexOf($scope.selectedEnumValues[0]);
+            if (index != 0) { // If it's the first, no moving up of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.selectedProperties[0].enumValues[index];
+                $scope.selectedProperties[0].enumValues.splice(index, 1);
+                $timeout(function(){
+                    $scope.selectedProperties[0].enumValues.splice(index + -1, 0, temp);
+                });
+
+            }
+        }
+    };
+
+    // Click handler for down button
+    $scope.moveEnumValueDown = function() {
+    	if ($scope.selectedProperties.length > 0 && $scope.selectedEnumValues.length > 0) {
+    		var index = $scope.selectedProperties[0].enumValues.indexOf($scope.selectedEnumValues[0]);
+            if (index != $scope.selectedProperties[0].enumValues.length - 1) { // If it's the last element, no moving down of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.selectedProperties[0].enumValues[index];
+                $scope.selectedProperties[0].enumValues.splice(index, 1);
+                $timeout(function(){
+                    $scope.selectedProperties[0].enumValues.splice(index + 1, 0, temp);
+                });
+
+            }
+        }
+    };
+
+    // Click handler for save button
+    $scope.save = function() {
+
+        if ($scope.formProperties.length > 0) {
+            $scope.property.value = {};
+            $scope.property.value.formProperties = $scope.formProperties;
+        } else {
+            $scope.property.value = null;
+        }
+
+        $scope.updatePropertyInModel($scope.property);
+        $scope.close();
+    };
+
+    $scope.cancel = function() {
+    	$scope.$hide();
+    	$scope.property.mode = 'read';
+    };
+
+    // Close button handler
+    $scope.close = function() {
+    	$scope.$hide();
+    	$scope.property.mode = 'read';
+    };
+
+}];

+ 158 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-in-parameters-controller.js

@@ -0,0 +1,158 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Input parameters for call activity
+ */
+
+var KisBpmInParametersCtrl = [ '$scope', '$modal', '$timeout', '$translate', function($scope, $modal, $timeout, $translate) {
+
+    // Config for the modal window
+    var opts = {
+        template:  'editor-app/configuration/properties/in-parameters-popup.html?version=' + Date.now(),
+        scope: $scope
+    };
+
+    // Open the dialog
+    $modal(opts);
+}];
+
+var KisBpmInParametersPopupCtrl = ['$scope', '$q', '$translate', function($scope, $q, $translate) {
+
+    // Put json representing form properties on scope
+    if ($scope.property.value !== undefined && $scope.property.value !== null
+        && $scope.property.value.inParameters !== undefined
+        && $scope.property.value.inParameters !== null) {
+        // Note that we clone the json object rather then setting it directly,
+        // this to cope with the fact that the user can click the cancel button and no changes should have happened
+        $scope.parameters = angular.copy($scope.property.value.inParameters);
+    } else {
+        $scope.parameters = [];
+    }
+
+    // Array to contain selected properties (yes - we only can select one, but ng-grid isn't smart enough)
+    $scope.selectedParameters = [];
+    $scope.translationsRetrieved = false;
+    
+    $scope.labels = {};
+    
+    var sourcePromise = $translate('PROPERTY.PARAMETER.SOURCE');
+    var sourceExpressionPromise = $translate('PROPERTY.PARAMETER.SOURCEEXPRESSION');
+    var targetPromise = $translate('PROPERTY.PARAMETER.TARGET');
+    
+    $q.all([sourcePromise, sourceExpressionPromise, targetPromise]).then(function(results) { 
+        $scope.labels.sourceLabel = results[0];
+        $scope.labels.sourceExpressionLabel = results[1];
+        $scope.labels.targetLabel = results[2];
+        $scope.translationsRetrieved = true;
+
+        // Config for grid
+        $scope.gridOptions = {
+            data: 'parameters',
+            enableRowReordering: true,
+            headerRowHeight: 28,
+            multiSelect: false,
+            keepLastSelected : false,
+            selectedItems: $scope.selectedParameters,
+            columnDefs: [{ field: 'source', displayName: $scope.labels.sourceLabel },
+                         { field: 'sourceExpression', displayName: $scope.labels.sourceExpressionLabel },
+                         { field: 'target', displayName: $scope.labels.targetLabel }]
+        };
+    });
+
+    // Click handler for add button
+    $scope.addNewParameter = function() {
+        $scope.parameters.push({ source : '',
+            sourceExpression : '',
+            target : ''});
+    };
+
+    // Click handler for remove button
+    $scope.removeParameter = function() {
+        if ($scope.selectedParameters.length > 0) {
+            var index = $scope.parameters.indexOf($scope.selectedParameters[0]);
+            $scope.gridOptions.selectItem(index, false);
+            $scope.parameters.splice(index, 1);
+
+            $scope.selectedParameters.length = 0;
+            if (index < $scope.parameters.length) {
+                $scope.gridOptions.selectItem(index + 1, true);
+            } else if ($scope.parameters.length > 0) {
+                $scope.gridOptions.selectItem(index - 1, true);
+            }
+        }
+    };
+
+    // Click handler for up button
+    $scope.moveParameterUp = function() {
+        if ($scope.selectedParameters.length > 0) {
+            var index = $scope.parameters.indexOf($scope.selectedParameters[0]);
+            if (index != 0) { // If it's the first, no moving up of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.parameters[index];
+                $scope.parameters.splice(index, 1);
+                $timeout(function(){
+                    $scope.parameters.splice(index + -1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for down button
+    $scope.moveParameterDown = function() {
+        if ($scope.selectedParameters.length > 0) {
+            var index = $scope.parameters.indexOf($scope.selectedParameters[0]);
+            if (index != $scope.parameters.length - 1) { // If it's the last element, no moving down of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.parameters[index];
+                $scope.parameters.splice(index, 1);
+                $timeout(function(){
+                    $scope.parameters.splice(index + 1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for save button
+    $scope.save = function() {
+
+        if ($scope.parameters.length > 0) {
+            $scope.property.value = {};
+            $scope.property.value.inParameters = $scope.parameters;
+        } else {
+            $scope.property.value = null;
+        }
+
+        $scope.updatePropertyInModel($scope.property);
+        $scope.close();
+    };
+
+    $scope.cancel = function() {
+        $scope.close();
+    };
+
+    // Close button handler
+    $scope.close = function() {
+        $scope.property.mode = 'read';
+        $scope.$hide();
+    };
+
+}];

+ 137 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-message-definitions-controller.js

@@ -0,0 +1,137 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Execution listeners
+ */
+
+angular.module('activitiModeler').controller('ActivitiMessageDefinitionsCtrl', ['$scope', '$modal', function ($scope, $modal) {
+
+    // Config for the modal window
+    var opts = {
+        template: 'editor-app/configuration/properties/message-definitions-popup.html?version=' + Date.now(),
+        scope: $scope
+    };
+
+    // Open the dialog
+    $modal(opts);
+}]);
+
+//Need a separate controller for the modal window due to https://github.com/angular-ui/bootstrap/issues/259
+// Will be fixed in a newer version of Angular UI
+angular.module('activitiModeler').controller('ActivitiMessageDefinitionsPopupCtrl',
+    ['$scope', '$q', '$translate', '$timeout', function ($scope, $q, $translate, $timeout) {
+
+        // Put json representing mesage definitions on scope
+        if ($scope.property.value !== undefined && $scope.property.value !== null && $scope.property.value.length > 0) {
+
+            if ($scope.property.value.constructor == String) {
+                $scope.messageDefinitions = JSON.parse($scope.property.value);
+            }
+            else {
+                // Note that we clone the json object rather then setting it directly,
+                // this to cope with the fact that the user can click the cancel button and no changes should have happened
+                $scope.messageDefinitions = angular.copy($scope.property.value);
+            }
+
+        } else {
+            $scope.messageDefinitions = [];
+        }
+
+        // Array to contain selected mesage definitions (yes - we only can select one, but ng-grid isn't smart enough)
+        $scope.selectedMessages = [];
+        $scope.translationsRetrieved = false;
+
+        $scope.labels = {};
+
+        var idPromise = $translate('PROPERTY.MESSAGEDEFINITIONS.ID');
+        var namePromise = $translate('PROPERTY.MESSAGEDEFINITIONS.NAME');
+
+        $q.all([idPromise, namePromise]).then(function (results) {
+
+            $scope.labels.idLabel = results[0];
+            $scope.labels.nameLabel = results[1];
+            $scope.translationsRetrieved = true;
+
+         // Config for grid
+            $scope.gridOptions = {
+                data: 'messageDefinitions',
+                headerRowHeight: 28,
+                enableRowSelection: true,
+                enableRowHeaderSelection: false,
+                multiSelect: false,
+                keepLastSelected : false,
+                selectedItems: $scope.selectedMessages,
+                columnDefs: [
+                    {field: 'id', displayName: $scope.labels.idLabel},
+                    {field: 'name', displayName: $scope.labels.nameLabel}]
+            };
+        });
+
+        // Click handler for add button
+        $scope.addNewMessageDefinition = function () {
+            var newMessageDefinition = {id: '', name: ''};
+
+            $scope.messageDefinitions.push(newMessageDefinition);
+            $timeout(function () {
+            	$scope.gridOptions.selectItem($scope.messageDefinitions.length - 1, true);
+            });
+        };
+
+        // Click handler for remove button
+        $scope.removeMessageDefinition = function () {
+        	if ($scope.selectedMessages && $scope.selectedMessages.length > 0) {
+            	var index = $scope.messageDefinitions.indexOf($scope.selectedMessages[0]);
+                $scope.gridOptions.selectItem(index, false);
+                $scope.messageDefinitions.splice(index, 1);
+
+                $scope.selectedMessages.length = 0;
+                if (index < $scope.messageDefinitions.length) {
+                    $scope.gridOptions.selectItem(index + 1, true);
+                } else if ($scope.messageDefinitions.length > 0) {
+                    $scope.gridOptions.selectItem(index - 1, true);
+                }
+            }
+        };
+
+        // Click handler for save button
+        $scope.save = function () {
+
+            if ($scope.messageDefinitions.length > 0) {
+                $scope.property.value = $scope.messageDefinitions;
+            } else {
+                $scope.property.value = null;
+            }
+
+            $scope.updatePropertyInModel($scope.property);
+            $scope.close();
+        };
+
+        $scope.cancel = function () {
+            $scope.property.mode = 'read';
+            $scope.$hide();
+        };
+
+        // Close button handler
+        $scope.close = function () {
+            $scope.property.mode = 'read';
+            $scope.$hide();
+        };
+
+    }]);

+ 48 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-message-scope-controller.js

@@ -0,0 +1,48 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+angular.module('activitiModeler').controller('ActivitiMessageRefCtrl', [ '$scope', function($scope) {
+
+    // Find the parent shape on which the message definitions are defined
+    var messageDefinitionsProperty = undefined;
+    var parent = $scope.selectedShape;
+    while (parent !== null && parent !== undefined && messageDefinitionsProperty === undefined) {
+        if (parent.properties && parent.properties['oryx-messagedefinitions']) {
+            messageDefinitionsProperty = parent.properties['oryx-messagedefinitions'];
+        } else {
+            parent = parent.parent;
+        }
+    }
+
+    try {
+        messageDefinitionsProperty = JSON.parse(messageDefinitionsProperty);
+        if (typeof messageDefinitionsProperty == 'string') {
+            messageDefinitionsProperty = JSON.parse(messageDefinitionsProperty);
+        }
+    } catch (err) {
+        // Do nothing here, just to be sure we try-catch it
+    }
+
+    $scope.messageDefinitions = messageDefinitionsProperty;
+
+
+    $scope.messageChanged = function() {
+    	$scope.updatePropertyInModel($scope.property);
+    };
+}]);

+ 34 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-multiinstance-controller.js

@@ -0,0 +1,34 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Execution listeners
+ */
+
+var KisBpmMultiInstanceCtrl = [ '$scope', function($scope) {
+
+    if ($scope.property.value == undefined && $scope.property.value == null)
+    {
+    	$scope.property.value = 'None';
+    }
+        
+    $scope.multiInstanceChanged = function() {
+    	$scope.updatePropertyInModel($scope.property);
+    };
+}];

+ 158 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-out-parameters-controller.js

@@ -0,0 +1,158 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Input parameters for call activity
+ */
+
+var KisBpmOutParametersCtrl = [ '$scope' , '$modal', '$timeout', '$translate', function($scope, $modal, $timeout, $translate) {
+
+    // Config for the modal window
+    var opts = {
+        template:  'editor-app/configuration/properties/out-parameters-popup.html?version=' + Date.now(),
+        scope: $scope
+    };
+
+    // Open the dialog
+    $modal(opts);
+}];
+
+var KisBpmOutParametersPopupCtrl = [ '$scope', '$q', '$translate', function($scope, $q, $translate) {
+
+    // Put json representing form properties on scope
+    if ($scope.property.value !== undefined && $scope.property.value !== null
+        && $scope.property.value.outParameters !== undefined
+        && $scope.property.value.outParameters !== null) {
+        // Note that we clone the json object rather then setting it directly,
+        // this to cope with the fact that the user can click the cancel button and no changes should have happened
+        $scope.parameters = angular.copy($scope.property.value.outParameters);
+    } else {
+        $scope.parameters = [];
+    }
+
+    // Array to contain selected properties (yes - we only can select one, but ng-grid isn't smart enough)
+    $scope.selectedParameters = [];
+    $scope.translationsRetrieved = false;
+    
+    $scope.labels = {};
+    
+    var sourcePromise = $translate('PROPERTY.PARAMETER.SOURCE');
+    var sourceExpressionPromise = $translate('PROPERTY.PARAMETER.SOURCEEXPRESSION');
+    var targetPromise = $translate('PROPERTY.PARAMETER.TARGET');
+    
+    $q.all([sourcePromise, sourceExpressionPromise, targetPromise]).then(function(results) { 
+        $scope.labels.sourceLabel = results[0];
+        $scope.labels.sourceExpressionLabel = results[1];
+        $scope.labels.targetLabel = results[2];
+        $scope.translationsRetrieved = true;
+        
+        // Config for grid
+        $scope.gridOptions = {
+            data: 'parameters',
+            enableRowReordering: true,
+            headerRowHeight: 28,
+            multiSelect: false,
+            keepLastSelected : false,
+            selectedItems: $scope.selectedParameters,
+            columnDefs: [{ field: 'source', displayName: $scope.labels.sourceLabel },
+                         { field: 'sourceExpression', displayName: $scope.labels.sourceExpressionLabel },
+                         { field: 'target', displayName: $scope.labels.targetLabel }]
+        };
+    });
+
+    // Click handler for add button
+    $scope.addNewParameter = function() {
+        $scope.parameters.push({ source : '',
+            sourceExpression : '',
+            target : ''});
+    };
+
+    // Click handler for remove button
+    $scope.removeParameter = function() {
+        if ($scope.selectedParameters.length > 0) {
+            var index = $scope.parameters.indexOf($scope.selectedParameters[0]);
+            $scope.gridOptions.selectItem(index, false);
+            $scope.parameters.splice(index, 1);
+
+            $scope.selectedParameters.length = 0;
+            if (index < $scope.parameters.length) {
+                $scope.gridOptions.selectItem(index + 1, true);
+            } else if ($scope.parameters.length > 0) {
+                $scope.gridOptions.selectItem(index - 1, true);
+            }
+        }
+    };
+
+    // Click handler for up button
+    $scope.moveParameterUp = function() {
+        if ($scope.selectedParameters.length > 0) {
+            var index = $scope.parameters.indexOf($scope.selectedParameters[0]);
+            if (index != 0) { // If it's the first, no moving up of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.parameters[index];
+                $scope.parameters.splice(index, 1);
+                $timeout(function(){
+                    $scope.parameters.splice(index + -1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for down button
+    $scope.moveParameterDown = function() {
+        if ($scope.selectedParameters.length > 0) {
+            var index = $scope.parameters.indexOf($scope.selectedParameters[0]);
+            if (index != $scope.parameters.length - 1) { // If it's the last element, no moving down of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.parameters[index];
+                $scope.parameters.splice(index, 1);
+                $timeout(function(){
+                    $scope.parameters.splice(index + 1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for save button
+    $scope.save = function() {
+
+        if ($scope.parameters.length > 0) {
+            $scope.property.value = {};
+            $scope.property.value.outParameters = $scope.parameters;
+        } else {
+            $scope.property.value = null;
+        }
+
+        $scope.updatePropertyInModel($scope.property);
+        $scope.close();
+    };
+
+    $scope.cancel = function() {
+        $scope.close();
+    };
+
+    // Close button handler
+    $scope.close = function() {
+        $scope.property.mode = 'read';
+        $scope.$hide();
+    };
+
+}];

+ 130 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-sequenceflow-order-controller.js

@@ -0,0 +1,130 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Sequence flow order controller
+ */
+
+var KisBpmSequenceFlowOrderCtrl = [ '$scope', '$modal', '$timeout', '$translate', function($scope, $modal, $timeout, $translate) {
+
+    // Config for the modal window
+    var opts = {
+        template:  'editor-app/configuration/properties/sequenceflow-order-popup.html?version=' + Date.now(),
+        scope: $scope
+    };
+
+    $modal(opts);
+}];
+
+var KisBpmSequenceFlowOrderPopupCtrl = ['$scope', '$translate', function($scope, $translate) {
+
+    // Find the outgoing sequence flow of the current selected shape
+    var outgoingSequenceFlow = [];
+    var selectedShape = $scope.selectedShape;
+    if (selectedShape) {
+        var outgoingNodes = selectedShape.getOutgoingShapes();
+        for (var i=0; i<outgoingNodes.length; i++) {
+            if (outgoingNodes[i].getStencil().title() === 'Sequence flow') {
+                var targetActivity = outgoingNodes[i].getTarget();
+                // We need the resourceId of a sequence flow, not the id because that will change with every editor load
+                outgoingSequenceFlow.push({
+                    id : outgoingNodes[i].resourceId,
+                    targetTitle : targetActivity.properties['oryx-name'],
+                    targetType : targetActivity.getStencil().title()
+                });
+            }
+        }
+    } else {
+        console.log('Programmatic error: no selected shape found');
+    }
+
+    // Now we can apply the order which was (possibly) previously saved
+    var orderedOutgoingSequenceFlow = [];
+    if ($scope.property.value && $scope.property.value.sequenceFlowOrder) {
+
+        var sequenceFlowOrderList = $scope.property.value.sequenceFlowOrder;
+
+        // Loop the list of sequence flow that was saved  in the json model and match them with the outgoing sequence flow found above
+        for (var flowIndex=0; flowIndex < sequenceFlowOrderList.length; flowIndex++) {
+
+            // find the sequence flow in the outgoing sequence flows.
+
+            for (var outgoingFlowIndex=0; outgoingFlowIndex < outgoingSequenceFlow.length; outgoingFlowIndex++) {
+                if (outgoingSequenceFlow[outgoingFlowIndex].id === sequenceFlowOrderList[flowIndex]) {
+                    orderedOutgoingSequenceFlow.push(outgoingSequenceFlow[outgoingFlowIndex]);
+                    outgoingSequenceFlow.splice(outgoingFlowIndex, 1);
+                    break;
+                }
+            }
+        }
+
+        // Now all the matching sequence flow we're removed from the outgoing sequence flow list
+        // We can simply apply the remaining ones (these are new vs. the time when the values were saved to the model)
+        orderedOutgoingSequenceFlow = orderedOutgoingSequenceFlow.concat(outgoingSequenceFlow);
+
+    } else {
+        orderedOutgoingSequenceFlow = outgoingSequenceFlow;
+    }
+
+    // Now we can put it on the scope
+    $scope.outgoingSequenceFlow = orderedOutgoingSequenceFlow;
+
+    // Move up click handler
+    $scope.moveUp = function(index) {
+        var temp = $scope.outgoingSequenceFlow[index];
+        $scope.outgoingSequenceFlow[index] = $scope.outgoingSequenceFlow[index - 1];
+        $scope.outgoingSequenceFlow[index - 1] = temp;
+    };
+
+    // Move down click handler
+    $scope.moveDown = function(index) {
+        var temp = $scope.outgoingSequenceFlow[index];
+        $scope.outgoingSequenceFlow[index] = $scope.outgoingSequenceFlow[index + 1];
+        $scope.outgoingSequenceFlow[index + 1] = temp;
+    };
+
+    // Save click handler
+    $scope.save = function() {
+        if ($scope.outgoingSequenceFlow.length > 0) {
+            $scope.property.value = {};
+            $scope.property.value.sequenceFlowOrder = [];
+
+            for (var flowIndex=0; flowIndex < $scope.outgoingSequenceFlow.length; flowIndex++) {
+                $scope.property.value.sequenceFlowOrder.push($scope.outgoingSequenceFlow[flowIndex].id);
+            }
+        } else {
+            $scope.property.value = null;
+        }
+
+        $scope.updatePropertyInModel($scope.property);
+        $scope.close();
+    };
+
+    // Cancel click handler
+    $scope.cancel = function() {
+        $scope.close();
+    };
+
+    // Close button handler
+    $scope.close = function() {
+        $scope.property.mode = 'read';
+        $scope.$hide();
+    };
+
+}];

+ 136 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-signal-definitions-controller.js

@@ -0,0 +1,136 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+angular.module('activitiModeler').controller('ActivitiSignalDefinitionsCtrl', ['$scope', '$modal', function ($scope, $modal) {
+
+    // Config for the modal window
+    var opts = {
+        template: 'editor-app/configuration/properties/signal-definitions-popup.html?version=' + Date.now(),
+        scope: $scope
+    };
+
+    // Open the dialog
+    $modal(opts);
+}]);
+
+//Need a separate controller for the modal window due to https://github.com/angular-ui/bootstrap/issues/259
+// Will be fixed in a newer version of Angular UI
+angular.module('activitiModeler').controller('ActivitiSignalDefinitionsPopupCtrl',
+    ['$scope', '$q', '$translate', '$timeout', function ($scope, $q, $translate, $timeout) {
+
+        // Put json representing signal definitions on scope
+        if ($scope.property.value !== undefined && $scope.property.value !== null && $scope.property.value.length > 0) {
+
+            if ($scope.property.value.constructor == String) {
+                $scope.signalDefinitions = JSON.parse($scope.property.value);
+            }
+            else {
+                // Note that we clone the json object rather then setting it directly,
+                // this to cope with the fact that the user can click the cancel button and no changes should have happened
+                $scope.signalDefinitions = angular.copy($scope.property.value);
+            }
+
+        } else {
+            $scope.signalDefinitions = [];
+        }
+
+        // Array to contain selected signal definitions (yes - we only can select one, but ng-grid isn't smart enough)
+        $scope.selectedSignals = [];
+        $scope.translationsRetrieved = false;
+
+        $scope.labels = {};
+
+        var idPromise = $translate('PROPERTY.SIGNALDEFINITIONS.ID');
+        var namePromise = $translate('PROPERTY.SIGNALDEFINITIONS.NAME');
+        var scopePromise = $translate('PROPERTY.SIGNALDEFINITIONS.SCOPE');
+
+        $q.all([idPromise, namePromise, scopePromise]).then(function (results) {
+
+            $scope.labels.idLabel = results[0];
+            $scope.labels.nameLabel = results[1];
+            $scope.labels.scopeLabel = results[2];
+            $scope.translationsRetrieved = true;
+
+            // Config for grid
+            $scope.gridOptions = {
+                data: 'signalDefinitions',
+                headerRowHeight: 28,
+                enableRowSelection: true,
+                enableRowHeaderSelection: false,
+                multiSelect: false,
+                keepLastSelected : false,
+                selectedItems: $scope.selectedSignals,
+                columnDefs: [
+                    {field: 'id', displayName: $scope.labels.idLabel},
+                    {field: 'name', displayName: $scope.labels.nameLabel},
+                    {field: 'scope', displayName: $scope.labels.scopeLabel}]
+            };
+        });
+
+        // Click handler for add button
+        $scope.addNewSignalDefinition = function () {
+            var newSignalDefinition = {id: '', name: '', scope: 'global'};
+
+            $scope.signalDefinitions.push(newSignalDefinition);
+            $timeout(function () {
+            	$scope.gridOptions.selectItem($scope.signalDefinitions.length - 1, true);
+            });
+        };
+
+        // Click handler for remove button
+        $scope.removeSignalDefinition = function () {
+            if ($scope.selectedSignals && $scope.selectedSignals.length > 0) {
+            	var index = $scope.signalDefinitions.indexOf($scope.selectedSignals[0]);
+                $scope.gridOptions.selectItem(index, false);
+                $scope.signalDefinitions.splice(index, 1);
+
+                $scope.selectedSignals.length = 0;
+                if (index < $scope.signalDefinitions.length) {
+                    $scope.gridOptions.selectItem(index + 1, true);
+                } else if ($scope.signalDefinitions.length > 0) {
+                    $scope.gridOptions.selectItem(index - 1, true);
+                }
+            }
+        };
+
+        // Click handler for save button
+        $scope.save = function () {
+
+            if ($scope.signalDefinitions.length > 0) {
+                $scope.property.value = $scope.signalDefinitions;
+            } else {
+                $scope.property.value = null;
+            }
+
+            $scope.updatePropertyInModel($scope.property);
+            $scope.close();
+        };
+
+        $scope.cancel = function () {
+            $scope.property.mode = 'read';
+            $scope.$hide();
+        };
+
+        // Close button handler
+        $scope.close = function () {
+            $scope.property.mode = 'read';
+            $scope.$hide();
+        };
+
+    }]);

+ 47 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-signal-scope-controller.js

@@ -0,0 +1,47 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+angular.module('activitiModeler').controller('ActivitiSignalRefCtrl', [ '$scope', function($scope) {
+
+    // Find the parent shape on which the signal definitions are defined
+    var signalDefinitionsProperty = undefined;
+    var parent = $scope.selectedShape;
+    while (parent !== null && parent !== undefined && signalDefinitionsProperty === undefined) {
+        if (parent.properties && parent.properties['oryx-signaldefinitions']) {
+            signalDefinitionsProperty = parent.properties['oryx-signaldefinitions'];
+        } else {
+            parent = parent.parent;
+        }
+    }
+
+    try {
+        signalDefinitionsProperty = JSON.parse(signalDefinitionsProperty);
+        if (typeof signalDefinitionsProperty == 'string') {
+            signalDefinitionsProperty = JSON.parse(signalDefinitionsProperty);
+        }
+    } catch (err) {
+        // Do nothing here, just to be sure we try-catch it
+    }
+
+    $scope.signalDefinitions = signalDefinitionsProperty;
+
+
+    $scope.signalChanged = function() {
+    	$scope.updatePropertyInModel($scope.property);
+    };
+}]);

+ 325 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties-task-listeners-controller.js

@@ -0,0 +1,325 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*
+ * Task listeners
+ */
+
+var KisBpmTaskListenersCtrl = [ '$scope', '$modal', '$timeout', '$translate', function($scope, $modal, $timeout, $translate) {
+
+    // Config for the modal window
+    var opts = {
+        template:  'editor-app/configuration/properties/task-listeners-popup.html?version=' + Date.now(),
+        scope: $scope
+    };
+
+    // Open the dialog
+    $modal(opts);
+}];
+
+var KisBpmTaskListenersPopupCtrl = [ '$scope', '$q', '$translate', function($scope, $q, $translate) {
+
+    // Put json representing form properties on scope
+    if ($scope.property.value !== undefined && $scope.property.value !== null
+        && $scope.property.value.taskListeners !== undefined
+        && $scope.property.value.taskListeners !== null) {
+        
+        if ($scope.property.value.taskListeners.constructor == String)
+        {
+            $scope.taskListeners = JSON.parse($scope.property.value.taskListeners);
+        }
+        else
+        {
+            // Note that we clone the json object rather then setting it directly,
+            // this to cope with the fact that the user can click the cancel button and no changes should have happened
+            $scope.taskListeners = angular.copy($scope.property.value.taskListeners);
+        }
+        
+        for (var i = 0; i < $scope.taskListeners.length; i++)
+        {
+            var taskListener = $scope.taskListeners[i];
+            if (taskListener.className !== undefined && taskListener.className !== '')
+            {
+                taskListener.implementation = taskListener.className;
+            }
+            else if (taskListener.expression !== undefined && taskListener.expression !== '')
+            {
+                taskListener.implementation = taskListener.expression;
+            }
+            else if (taskListener.delegateExpression !== undefined && taskListener.delegateExpression !== '')
+            {
+                taskListener.implementation = taskListener.delegateExpression;
+            }
+        }
+    } else {
+        $scope.taskListeners = [];
+    }
+
+    // Array to contain selected properties (yes - we only can select one, but ng-grid isn't smart enough)
+    $scope.selectedListeners = [];
+    $scope.selectedFields = [];
+    $scope.translationsRetrieved = false;
+    
+    $scope.labels = {};
+    
+    var eventPromise = $translate('PROPERTY.TASKLISTENERS.EVENT');
+    var implementationPromise = $translate('PROPERTY.TASKLISTENERS.FIELDS.IMPLEMENTATION');
+    var namePromise = $translate('PROPERTY.TASKLISTENERS.FIELDS.NAME');
+    
+    $q.all([eventPromise, implementationPromise, namePromise]).then(function(results) { 
+        $scope.labels.eventLabel = results[0];
+        $scope.labels.implementationLabel = results[1];
+        $scope.labels.nameLabel = results[2];
+        $scope.translationsRetrieved = true;
+
+        // Config for grid
+        $scope.gridOptions = {
+            data: 'taskListeners',
+            enableRowReordering: true,
+            headerRowHeight: 28,
+            multiSelect: false,
+            keepLastSelected : false,
+            selectedItems: $scope.selectedListeners,
+            afterSelectionChange: function (rowItem, event) {
+                $scope.selectedFields.length = 0;
+                if ($scope.selectedListeners.length > 0)
+                {
+                    var fields = $scope.selectedListeners[0].fields;
+                    if (fields !== undefined && fields !== null)
+                    {
+                        for (var i = 0; i < fields.length; i++)
+                        {
+                            var field = fields[i];
+                            if (field.stringValue !== undefined && field.stringValue !== '')
+                            {
+                                field.implementation = field.stringValue;
+                            }
+                            else if (field.expression !== undefined && field.expression !== '')
+                            {
+                                field.implementation = field.expression;
+                            }
+                            else if (field.string !== undefined && field.string !== '')
+                            {
+                                field.implementation = field.string;
+                            }
+                        }
+                    }
+                }
+            },
+            columnDefs: [{ field: 'event', displayName: $scope.labels.eventLabel },
+                { field: 'implementation', displayName: $scope.labels.implementationLabel}]
+        };
+        
+        // Config for field grid
+        $scope.gridFieldOptions = {
+            data: 'selectedListeners[0].fields',
+            enableRowReordering: true,
+            headerRowHeight: 28,
+            multiSelect: false,
+            keepLastSelected : false,
+            selectedItems: $scope.selectedFields,
+            columnDefs: [{ field: 'name', displayName: $scope.labels.name },
+                { field: 'implementation', displayName: $scope.labels.implementationLabel}]
+        };
+    });
+    
+    $scope.listenerDetailsChanged = function() {
+        if ($scope.selectedListeners[0].className !== '')
+        {
+            $scope.selectedListeners[0].implementation = $scope.selectedListeners[0].className;
+        }
+        else if ($scope.selectedListeners[0].expression !== '')
+        {
+            $scope.selectedListeners[0].implementation = $scope.selectedListeners[0].expression;
+        }
+        else if ($scope.selectedListeners[0].delegateExpression !== '')
+        {
+            $scope.selectedListeners[0].implementation = $scope.selectedListeners[0].delegateExpression;
+        }
+        else
+        {
+            $scope.selectedListeners[0].implementation = '';
+        }
+    };
+
+    // Click handler for add button
+    $scope.addNewListener = function() {
+        $scope.taskListeners.push({ event : 'create',
+            implementation : '',
+            className : '',
+            expression: '',
+            delegateExpression: ''});
+    };
+
+    // Click handler for remove button
+    $scope.removeListener = function() {
+        if ($scope.selectedListeners.length > 0) {
+            var index = $scope.taskListeners.indexOf($scope.selectedListeners[0]);
+            $scope.gridOptions.selectItem(index, false);
+            $scope.taskListeners.splice(index, 1);
+
+            $scope.selectedListeners.length = 0;
+            if (index < $scope.taskListeners.length) {
+                $scope.gridOptions.selectItem(index + 1, true);
+            } else if ($scope.taskListeners.length > 0) {
+                $scope.gridOptions.selectItem(index - 1, true);
+            }
+        }
+    };
+
+    // Click handler for up button
+    $scope.moveListenerUp = function() {
+        if ($scope.selectedListeners.length > 0) {
+            var index = $scope.taskListeners.indexOf($scope.selectedListeners[0]);
+            if (index != 0) { // If it's the first, no moving up of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.taskListeners[index];
+                $scope.taskListeners.splice(index, 1);
+                $timeout(function(){
+                    $scope.taskListeners.splice(index + -1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for down button
+    $scope.moveListenerDown = function() {
+        if ($scope.selectedListeners.length > 0) {
+            var index = $scope.taskListeners.indexOf($scope.selectedListeners[0]);
+            if (index != $scope.taskListeners.length - 1) { // If it's the last element, no moving down of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.taskListeners[index];
+                $scope.taskListeners.splice(index, 1);
+                $timeout(function(){
+                    $scope.taskListeners.splice(index + 1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+    
+    $scope.fieldDetailsChanged = function() {
+        if ($scope.selectedFields[0].stringValue != '')
+        {
+            $scope.selectedFields[0].implementation = $scope.selectedFields[0].stringValue;
+        }
+        else if ($scope.selectedFields[0].expression != '')
+        {
+            $scope.selectedFields[0].implementation = $scope.selectedFields[0].expression;
+        }
+        else if ($scope.selectedFields[0].string != '')
+        {
+            $scope.selectedFields[0].implementation = $scope.selectedFields[0].string;
+        }
+        else
+        {
+            $scope.selectedFields[0].implementation = '';
+        }
+    };
+
+    // Click handler for add button
+    $scope.addNewField = function() {
+        if ($scope.selectedListeners.length > 0)
+        {
+            if ($scope.selectedListeners[0].fields == undefined)
+            {
+                $scope.selectedListeners[0].fields = [];
+            }
+            $scope.selectedListeners[0].fields.push({ name : 'fieldName',
+                implementation : '',
+                stringValue : '',
+                expression: '',
+                string: ''});
+        }
+    };
+
+    // Click handler for remove button
+    $scope.removeField = function() {
+        if ($scope.selectedFields.length > 0) {
+            var index = $scope.selectedListeners[0].fields.indexOf($scope.selectedFields[0]);
+            $scope.gridFieldOptions.selectItem(index, false);
+            $scope.selectedListeners[0].fields.splice(index, 1);
+
+            $scope.selectedFields.length = 0;
+            if (index < $scope.selectedListeners[0].fields.length) {
+                $scope.gridFieldOptions.selectItem(index + 1, true);
+            } else if ($scope.selectedListeners[0].fields.length > 0) {
+                $scope.gridFieldOptions.selectItem(index - 1, true);
+            }
+        }
+    };
+
+    // Click handler for up button
+    $scope.moveFieldUp = function() {
+        if ($scope.selectedFields.length > 0) {
+            var index = $scope.selectedListeners[0].fields.indexOf($scope.selectedFields[0]);
+            if (index != 0) { // If it's the first, no moving up of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.selectedListeners[0].fields[index];
+                $scope.selectedListeners[0].fields.splice(index, 1);
+                $timeout(function(){
+                    $scope.selectedListeners[0].fields.splice(index + -1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for down button
+    $scope.moveFieldDown = function() {
+        if ($scope.selectedFields.length > 0) {
+            var index = $scope.selectedListeners[0].fields.indexOf($scope.selectedFields[0]);
+            if (index != $scope.selectedListeners[0].fields.length - 1) { // If it's the last element, no moving down of course
+                // Reason for funny way of swapping, see https://github.com/angular-ui/ng-grid/issues/272
+                var temp = $scope.selectedListeners[0].fields[index];
+                $scope.selectedListeners[0].fields.splice(index, 1);
+                $timeout(function(){
+                    $scope.selectedListeners[0].fields.splice(index + 1, 0, temp);
+                }, 100);
+
+            }
+        }
+    };
+
+    // Click handler for save button
+    $scope.save = function() {
+
+        if ($scope.taskListeners.length > 0) {
+            $scope.property.value = {};
+            $scope.property.value.taskListeners = $scope.taskListeners;
+        } else {
+            $scope.property.value = null;
+        }
+
+        $scope.updatePropertyInModel($scope.property);
+        $scope.close();
+    };
+
+    $scope.cancel = function() {
+        $scope.close();
+    };
+
+    // Close button handler
+    $scope.close = function() {
+        $scope.property.mode = 'read';
+        $scope.$hide();
+    };
+
+}];

+ 99 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties.js

@@ -0,0 +1,99 @@
+/*
+ * Activiti Modeler component part of the Activiti project
+ * Copyright 2005-2014 Alfresco Software, Ltd. All rights reserved.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+'use strict';
+
+var KISBPM = KISBPM || {};
+KISBPM.PROPERTY_CONFIG =
+{
+    "string": {
+        "readModeTemplateUrl": "editor-app/configuration/properties/default-value-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/string-property-write-mode-template.html"
+    },
+    "boolean": {
+        "templateUrl": "editor-app/configuration/properties/boolean-property-template.html"
+    },
+    "text" : {
+        "readModeTemplateUrl": "editor-app/configuration/properties/default-value-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/text-property-write-template.html"
+    },
+    "kisbpm-multiinstance" : {
+        "readModeTemplateUrl": "editor-app/configuration/properties/default-value-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/multiinstance-property-write-template.html"
+    },
+    "oryx-formproperties-complex": {
+        "readModeTemplateUrl": "editor-app/configuration/properties/form-properties-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/form-properties-write-template.html"
+    },
+    "oryx-executionlisteners-multiplecomplex": {
+        "readModeTemplateUrl": "editor-app/configuration/properties/execution-listeners-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/execution-listeners-write-template.html"
+    },
+    "oryx-tasklisteners-multiplecomplex": {
+        "readModeTemplateUrl": "editor-app/configuration/properties/task-listeners-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/task-listeners-write-template.html"
+    },
+    "oryx-eventlisteners-multiplecomplex": {
+        "readModeTemplateUrl": "editor-app/configuration/properties/event-listeners-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/event-listeners-write-template.html"
+    },
+    "oryx-usertaskassignment-complex": {
+        "readModeTemplateUrl": "editor-app/configuration/properties/assignment-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/assignment-write-template.html"
+    },
+    "oryx-servicetaskfields-complex": {
+        "readModeTemplateUrl": "editor-app/configuration/properties/fields-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/fields-write-template.html"
+    },
+    "oryx-callactivityinparameters-complex": {
+        "readModeTemplateUrl": "editor-app/configuration/properties/in-parameters-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/in-parameters-write-template.html"
+    },
+    "oryx-callactivityoutparameters-complex": {
+        "readModeTemplateUrl": "editor-app/configuration/properties/out-parameters-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/out-parameters-write-template.html"
+    },
+    "oryx-subprocessreference-complex": {
+        "readModeTemplateUrl": "editor-app/configuration/properties/subprocess-reference-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/subprocess-reference-write-template.html"
+    },
+    "oryx-sequencefloworder-complex" : {
+        "readModeTemplateUrl": "editor-app/configuration/properties/sequenceflow-order-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/sequenceflow-order-write-template.html"
+    },
+    "oryx-conditionsequenceflow-complex" : {
+        "readModeTemplateUrl": "editor-app/configuration/properties/condition-expression-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/condition-expression-write-template.html"
+    },
+    "oryx-signaldefinitions-multiplecomplex" : {
+        "readModeTemplateUrl": "editor-app/configuration/properties/signal-definitions-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/signal-definitions-write-template.html"
+    },
+    "oryx-signalref-string" : {
+        "readModeTemplateUrl": "editor-app/configuration/properties/default-value-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/signal-property-write-template.html"
+    },
+    "oryx-messagedefinitions-multiplecomplex" : {
+        "readModeTemplateUrl": "editor-app/configuration/properties/message-definitions-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/message-definitions-write-template.html"
+    },
+    "oryx-messageref-string" : {
+        "readModeTemplateUrl": "editor-app/configuration/properties/default-value-display-template.html",
+        "writeModeTemplateUrl": "editor-app/configuration/properties/message-property-write-template.html"
+    }
+};

+ 4 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/assignment-display-template.html

@@ -0,0 +1,4 @@
+<span ng-if="property.value.assignment.assignee">{{'PROPERTY.ASSIGNMENT.ASSIGNEE_DISPLAY' | translate:property.value.assignment }} </span>
+<span ng-if="property.value.assignment.candidateUsers.length > 0">{{'PROPERTY.ASSIGNMENT.CANDIDATE_USERS_DISPLAY' | translate:property.value.assignment.candidateUsers}} </span>
+<span ng-if="property.value.assignment.candidateGroups.length > 0">{{'PROPERTY.ASSIGNMENT.CANDIDATE_GROUPS_DISPLAY' | translate:property.value.assignment.candidateGroups}} </span>
+<span ng-if="!property.value.assignment.assignee && (!property.value.assignment.candidateUsers || property.value.assignment.candidateUsers.length == 0) && (!property.value.assignment.candidateGroups || property.value.assignment.candidateGroups.length == 0)" translate>PROPERTY.ASSIGNMENT.EMPTY</span>

+ 44 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/assignment-popup.html

@@ -0,0 +1,44 @@
+<div class="modal" ng-controller="KisBpmAssignmentPopupCtrl">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">&times;</button>
+                <h2 translate>PROPERTY.ASSIGNMENT.TITLE</h2>
+            </div>
+            <div class="modal-body">
+            
+            	<div class="row row-no-gutter">
+            		<div class="form-group">
+            			<label for="assigneeField">{{'PROPERTY.ASSIGNMENT.ASSIGNEE' | translate}}</label>
+            			<input type="text" id="assigneeField" class="form-control" ng-model="assignment.assignee" placeholder="{{'PROPERTY.ASSIGNMENT.ASSIGNEE_PLACEHOLDER' | translate}}" />
+            		</div>
+            	</div>
+            	
+                <div class="row row-no-gutter">
+                    <div class="form-group">
+                    	<label for="userField">{{'PROPERTY.ASSIGNMENT.CANDIDATE_USERS' | translate}}</label>
+                        <div ng-repeat="candidateUser in assignment.candidateUsers">
+            	            <input id="userField" class="form-control" type="text" ng-model="candidateUser.value" />
+            	            <i class="glyphicon glyphicon-minus clickable-property" ng-click="removeCandidateUserValue($index)"></i>
+            	            <i ng-if="$index == (assignment.candidateUsers.length - 1)" class="glyphicon glyphicon-plus clickable-property" ng-click="addCandidateUserValue($index)"></i>
+                        </div>
+                   	</div>
+            
+                    <div class="form-group">
+                    	<label for="groupField">{{'PROPERTY.ASSIGNMENT.CANDIDATE_GROUPS' | translate}}</label>
+                        <div ng-repeat="candidateGroup in assignment.candidateGroups">
+            	          	<input id="groupField" class="form-control" type="text" ng-model="candidateGroup.value" />
+            	          	<i class="glyphicon glyphicon-minus clickable-property" ng-click="removeCandidateGroupValue($index)"></i>
+            	          	<i ng-if="$index == (assignment.candidateGroups.length - 1)" class="glyphicon glyphicon-plus clickable-property" ng-click="addCandidateGroupValue($index)"></i>
+                        </div>
+                    </div>
+                </div>
+            
+            </div>
+            <div class="modal-footer">
+                <button ng-click="close()" class="btn btn-primary" translate>ACTION.CANCEL</button>
+                <button ng-click="save()" class="btn btn-primary" translate>ACTION.SAVE</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 4 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/assignment-write-template.html

@@ -0,0 +1,4 @@
+
+<!-- Just need to instantiate the controller, and it will take care of showing the modal dialog -->
+<span ng-controller="KisBpmAssignmentCtrl">
+</span>

+ 4 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/boolean-property-template.html

@@ -0,0 +1,4 @@
+
+<div ng-controller="KisBpmBooleanPropertyCtrl">
+    <input type="checkbox" ng-model="property.value" ng-change="changeValue()"/>
+</div>

+ 2 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/condition-expression-display-template.html

@@ -0,0 +1,2 @@
+<span ng-if="property.value">{{property.value|limitTo:20}}</span>
+<span ng-if="!property.value">{{'PROPERTY.SEQUENCEFLOW.CONDITION.NO-CONDITION-DISPLAY' | translate}}</span>

+ 29 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/condition-expression-popup.html

@@ -0,0 +1,29 @@
+
+<div class="modal" ng-controller="KisBpmConditionExpressionPopupCtrl">
+<div class="modal-dialog">
+<div class="modal-content">
+<div class="modal-header">
+    <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">&times;</button>
+    <h2 translate>PROPERTY.SEQUENCEFLOW.CONDITION.TITLE</h2>
+</div>
+<div class="modal-body">
+
+    <div class="detail-group clearfix">
+        
+        <div class="form-group clearfix">
+            <div class="col-xs-12">
+                <label class="col-xs-3">{{'PROPERTY.SEQUENCEFLOW.CONDITION.STATIC' | translate}}</label>
+                <div class="col-xs-9">
+                    <textarea class="form-control" ng-model="conditionExpression.value" style="width:90%; height:100%; max-width: 100%; max-height: 100%; min-height: 100px"/>
+                </div>
+            </div>
+        </div>
+
+    </div>
+    <div class="modal-footer">
+        <button ng-click="close()" class="btn btn-primary" translate>ACTION.CANCEL</button>
+        <button ng-click="save()" class="btn btn-primary" translate>ACTION.SAVE</button>
+    </div>
+</div>
+</div>
+</div>

+ 4 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/condition-expression-write-template.html

@@ -0,0 +1,4 @@
+
+<!-- Just need to instantiate the controller, and it will take care of showing the modal dialog -->
+<span ng-controller="KisBpmConditionExpressionCtrl">
+</span>

+ 4 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/default-value-display-template.html

@@ -0,0 +1,4 @@
+
+<span ng-if="!property.noValue">{{property.value|limitTo:20}}</span>
+<span ng-if="!property.noValue && property.value != null && property.value.length > 20">...</span>
+<span ng-if="property.noValue" translate>PROPERTY.EMPTY</span>

+ 3 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/event-listeners-display-template.html

@@ -0,0 +1,3 @@
+
+<span ng-if="!property.noValue">{{'PROPERTY.EVENTLISTENERS.DISPLAY' | translate:property.value.eventListeners}}</span>
+<span ng-if="property.noValue" translate>PROPERTY.EVENTLISTENERS.EMPTY</span>

+ 115 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/event-listeners-popup.html

@@ -0,0 +1,115 @@
+
+<div class="modal" ng-controller="KisBpmEventListenersPopupCtrl">
+    <div class="modal-dialog modal-wide">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">&times;</button>
+                <h2>{{'PROPERTY.PROPERTY.EDIT.TITLE' | translate:property}}</h2>
+            </div>
+            <div class="modal-body">
+            
+                <div class="row row-no-gutter">
+                	<div class="col-xs-10">
+            	        <div ng-if="translationsRetrieved" class="kis-listener-grid" ng-grid="gridOptions"></div>
+            	        <div class="pull-right">
+            	            <div class="btn-group">
+            	                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.UP | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveListenerUp()"><i class="glyphicon glyphicon-arrow-up"></i></a>
+            	                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.DOWN | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveListenerDown()"><i class="glyphicon glyphicon-arrow-down"></i></a>
+            	            </div>
+            	            <div class="btn-group">
+            	                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.ADD | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="addNewListener()"><i class="glyphicon glyphicon-plus"></i></a>
+            	                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.REMOVE | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="removeListener()"><i class="glyphicon glyphicon-minus"></i></a>
+            	            </div>
+            	        </div>
+            		</div>
+            	</div>
+            	
+            	<div class="row row-no-gutter">
+                  	<div ng-if="translationsRetrieved" ng-show="selectedListeners.length > 0" class="col-xs-6">
+            			<div class="form-group">
+            	        	<label for="userField">{{'PROPERTY.EVENTLISTENERS.EVENTS' | translate}}</label>
+            	            <div ng-repeat="eventDefinition in selectedListeners[0].events">
+            	            	<select id="eventField" class="form-control" ng-model="eventDefinition.event" ng-change="listenerDetailsChanged()">
+            	            		<option title="{{'EVENT_TYPE.ACTIVITY.COMPENSATE.TOOLTIP' | translate}}">ACTIVITY_COMPENSATE</option>
+            	            		<option title="{{'EVENT_TYPE.ACTIVITY.COMPLETED.TOOLTIP' | translate}}">ACTIVITY_COMPLETED</option>
+            	            		<option title="bla">ACTIVITY_ERROR_RECEIVED</option>
+            	            		<option>ACTIVITY_MESSAGE_RECEIVED</option>
+            	            		<option>ACTIVITY_SIGNALED</option>
+            	            		<option>ACTIVITY_STARTED</option>
+            	            		<option>ENGINE_CLOSED</option>
+            	            		<option>ENGINE_CREATED</option>
+            	            		<option>ENTITY_ACTIVATED</option>
+            	                	<option>ENTITY_CREATED</option>
+            	                	<option>ENTITY_DELETED</option>
+            	                	<option>ENTITY_INITIALIZED</option>
+            	                	<option>ENTITY_SUSPENDED</option>
+            	                	<option>ENTITY_UPDATED</option>
+            	                	<option>JOB_EXECUTION_FAILURE</option>
+            	                	<option>JOB_EXECUTION_SUCCESS</option>
+            	                	<option>JOB_RETRIES_DECREMENTED</option>
+            	                	<option title="{{'EVENT_TYPE.MEMBERSHIP.CREATED.TOOLTIP' | translate}}">MEMBERSHIP_CREATED</option>
+            	                	<option title="{{'EVENT_TYPE.MEMBERSHIP.DELETED.TOOLTIP' | translate}}">MEMBERSHIP_DELETED</option>
+            	                	<option title="{{'EVENT_TYPE.MEMBERSHIPS.DELETED.TOOLTIP' | translate}}">MEMBERSHIPS_DELETED</option>
+            	                	<option title="{{'EVENT_TYPE.TASK.ASSIGNED.TOOLTIP' | translate}}">TASK_ASSIGNED</option>
+            	                	<option title="{{'EVENT_TYPE.TASK.COMPLETED.TOOLTIP' | translate}}">TASK_COMPLETED</option>
+            	                	<option>TIMER_FIRED</option>
+            	                	<option title="{{'EVENT_TYPE.UNCAUGHT.BPMNERROR.TOOLTIP' | translate}}">UNCAUGHT_BPMN_ERROR</option>
+            	                	<option title="{{'EVENT_TYPE.VARIABLE.CREATED.TOOLTIP' | translate}}">VARIABLE_CREATED</option>
+            	                	<option title="{{'EVENT_TYPE.VARIABLE.DELETED.TOOLTIP' | translate}}">VARIABLE_DELETED</option>
+            	                	<option title="{{'EVENT_TYPE.VARIABLE.UPDATED.TOOLTIP' | translate}}">VARIABLE_UPDATED</option>
+            	               	</select>
+            		            <i ng-if="$index > 0" class="glyphicon glyphicon-minus clickable-property" ng-click="removeEventValue($index)"></i>
+            		            <i class="glyphicon glyphicon-plus clickable-property" ng-click="addEventValue($index)"></i>
+            	            </div>
+            	            <div class="form-group">
+            			   		<label for="classField">{{'PROPERTY.EVENTLISTENERS.RETHROW' | translate}}</label>
+            			   		<input type="checkbox" id="rethrowField" class="form-control" ng-model="selectedListeners[0].rethrowEvent" ng-change="listenerDetailsChanged()" />
+            				</div>
+            	       	</div>
+                     </div>
+                     <div ng-show="selectedListeners.length > 0 && selectedListeners[0].events[0].event" class="col-xs-6">
+                     	<div class="form-group" ng-if="!selectedListeners[0].rethrowEvent">
+            		   		<label for="classField">{{'PROPERTY.EVENTLISTENERS.CLASS' | translate}}</label>
+            		   		<input type="text" id="classField" class="form-control" ng-model="selectedListeners[0].className" ng-change="listenerDetailsChanged()" placeholder="{{'PROPERTY.EVENTLISTENERS.CLASS.PLACEHOLDER' | translate}}" />
+            			</div>
+            			<div class="form-group" ng-if="!selectedListeners[0].rethrowEvent">
+            		   		<label for="delegateExpressionField">{{'PROPERTY.EVENTLISTENERS.DELEGATEEXPRESSION' | translate}}</label>
+            		   		<input type="text" id="delegateExpressionField" class="form-control" ng-model="selectedListeners[0].delegateExpression" ng-change="listenerDetailsChanged()" placeholder="{{'PROPERTY.EVENTLISTENERS.DELEGATEEXPRESSION.PLACEHOLDER' | translate}}" />
+            			</div>
+            			<div class="form-group" ng-if="!selectedListeners[0].rethrowEvent">
+            		   		<label for="entityTypeField">{{'PROPERTY.EVENTLISTENERS.ENTITYTYPE' | translate}}</label>
+            		   		<input type="text" id="entityTypeField" class="form-control" ng-model="selectedListeners[0].entityType" ng-change="listenerDetailsChanged()" placeholder="{{'PROPERTY.EVENTLISTENERS.ENTITYTYPE.PLACEHOLDER' | translate}}" />
+            			</div>
+            			<div class="form-group" ng-if="selectedListeners[0].rethrowEvent">
+            		   		<label for="delegateExpressionField">{{'PROPERTY.EVENTLISTENERS.RETHROWTYPE' | translate}}</label>
+            		   		<select id="rethrowTypeField" class="form-control" ng-model="selectedListeners[0].rethrowType" ng-change="rethrowTypeChanged()">
+                                <option>error</option>
+                                <option>message</option>
+                                <option>signal</option>
+                                <option>globalSignal</option>
+                            </select>
+            			</div>
+            			<div class="form-group" ng-if="selectedListeners[0].rethrowType === 'error'">
+            		   		<label for="errorCodeField">{{'PROPERTY.EVENTLISTENERS.ERRORCODE' | translate}}</label>
+            		   		<input type="text" id="errorCodeField" class="form-control" ng-model="selectedListeners[0].errorcode" ng-change="listenerDetailsChanged()" placeholder="{{'PROPERTY.EVENTLISTENERS.ERRORCODE.PLACEHOLDER' | translate}}" />
+            			</div>
+            			<div class="form-group" ng-if="selectedListeners[0].rethrowType === 'message'">
+            		   		<label for="messageNameField">{{'PROPERTY.EVENTLISTENERS.MESSAGENAME' | translate}}</label>
+            		   		<input type="text" id="messageNameField" class="form-control" ng-model="selectedListeners[0].messagename" ng-change="listenerDetailsChanged()" placeholder="{{'PROPERTY.EVENTLISTENERS.MESSAGENAME.PLACEHOLDER' | translate}}" />
+            			</div>
+            			<div class="form-group" ng-if="selectedListeners[0].rethrowType === 'signal' || selectedListeners[0].rethrowType === 'globalSignal'">
+            		   		<label for="messageNameField">{{'PROPERTY.EVENTLISTENERS.SIGNALNAME' | translate}}</label>
+            		   		<input type="text" id="signalNameField" class="form-control" ng-model="selectedListeners[0].signalname" ng-change="listenerDetailsChanged()" placeholder="{{'PROPERTY.EVENTLISTENERS.SIGNALNAME.PLACEHOLDER' | translate}}" />
+            			</div>
+                     </div>
+                     <div ng-show="selectedListeners.length == 0" class="col-xs-6 muted no-property-selected" translate>PROPERTY.EVENTLISTENERS.UNSELECTED</div>
+                </div>
+            
+            </div>
+            <div class="modal-footer">
+                <button ng-click="cancel()" class="btn btn-primary" translate>ACTION.CANCEL</button>
+                <button ng-click="save()" class="btn btn-primary" translate>ACTION.SAVE</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 4 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/event-listeners-write-template.html

@@ -0,0 +1,4 @@
+
+<!-- Just need to instantiate the controller, and it will take care of showing the modal dialog -->
+<span ng-controller="KisBpmEventListenersCtrl">
+</span>

+ 3 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/execution-listeners-display-template.html

@@ -0,0 +1,3 @@
+
+<span ng-if="!property.noValue">{{'PROPERTY.EXECUTIONLISTENERS.DISPLAY' | translate:property.value.executionListeners}}</span>
+<span ng-if="property.noValue" translate>PROPERTY.EXECUTIONLISTENERS.EMPTY</span>

+ 101 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/execution-listeners-popup.html

@@ -0,0 +1,101 @@
+
+<div class="modal" ng-controller="KisBpmExecutionListenersPopupCtrl">
+    <div class="modal-dialog modal-wide">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">&times;</button>
+                <h2>{{'PROPERTY.PROPERTY.EDIT.TITLE' | translate:property}}</h2>
+            </div>
+            <div class="modal-body">
+            
+                <div class="row row-no-gutter">
+                    <div class="col-xs-6">
+                        <div ng-if="translationsRetrieved" class="kis-listener-grid" ng-grid="gridOptions"></div>
+                        <div class="pull-right">
+                            <div class="btn-group">
+                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.UP | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveListenerUp()"><i class="glyphicon glyphicon-arrow-up"></i></a>
+                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.DOWN | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveListenerDown()"><i class="glyphicon glyphicon-arrow-down"></i></a>
+                            </div>
+                            <div class="btn-group">
+                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.ADD | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="addNewListener()"><i class="glyphicon glyphicon-plus"></i></a>
+                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.REMOVE | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="removeListener()"><i class="glyphicon glyphicon-minus"></i></a>
+                            </div>
+                        </div>
+                    </div>
+            
+                    <div class="col-xs-6">
+                        <div ng-show="selectedListeners.length > 0">
+            
+                            <div class="form-group">
+            			   		<label for="eventField">{{'PROPERTY.EXECUTIONLISTENERS.EVENT' | translate}}</label>
+            			   		<select id="eventField" class="form-control" ng-model="selectedListeners[0].event">
+                                    <option>start</option>
+                                    <option>end</option>
+                                    <option>take</option>
+                                </select>
+            				</div>
+            				<div class="form-group">
+            			   		<label for="classField">{{'PROPERTY.EXECUTIONLISTENERS.CLASS' | translate}}</label>
+            			   		<input type="text" id="classField" class="form-control" ng-model="selectedListeners[0].className" ng-change="listenerDetailsChanged()" placeholder="{{'PROPERTY.EXECUTIONLISTENERS.CLASS.PLACEHOLDER' | translate}}" />
+            				</div>
+            				<div class="form-group">
+            			   		<label for="expressionField">{{'PROPERTY.EXECUTIONLISTENERS.EXPRESSION' | translate}}</label>
+            			   		<input type="text" id="expressionField" class="form-control" ng-model="selectedListeners[0].expression" ng-change="listenerDetailsChanged()" placeholder="{{'PROPERTY.EXECUTIONLISTENERS.EXPRESSION.PLACEHOLDER' | translate}}" />
+            				</div>
+            				<div class="form-group">
+            			   		<label for="delegateExpressionField">{{'PROPERTY.EXECUTIONLISTENERS.DELEGATEEXPRESSION' | translate}}</label>
+            			   		<input type="text" id="delegateExpressionField" class="form-control" ng-model="selectedListeners[0].delegateExpression" ng-change="listenerDetailsChanged()" placeholder="{{'PROPERTY.EXECUTIONLISTENERS.DELEGATEEXPRESSION.PLACEHOLDER' | translate}}" />
+            				</div>
+                        </div>
+                        <div ng-show="selectedListeners.length == 0" class="muted no-property-selected" translate>PROPERTY.EXECUTIONLISTENERS.UNSELECTED</div>
+                    </div>
+                </div>
+                
+                <div class="row row-no-gutter">
+                    <div class="col-xs-6">
+                        <div ng-if="translationsRetrieved" class="kis-field-grid" ng-grid="gridFieldOptions"></div>
+                        <div class="pull-right">
+                            <div class="btn-group">
+                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.UP | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveFieldUp()"><i class="glyphicon glyphicon-arrow-up"></i></a>
+                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.DOWN | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveFieldDown()"><i class="glyphicon glyphicon-arrow-down"></i></a>
+                            </div>
+                            <div class="btn-group">
+                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.ADD | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="addNewField()"><i class="glyphicon glyphicon-plus"></i></a>
+                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.REMOVE | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="removeField()"><i class="glyphicon glyphicon-minus"></i></a>
+                            </div>
+                        </div>
+                    </div>
+            
+                    <div class="col-xs-6">
+                        <div ng-show="selectedFields.length > 0">
+            
+            				<div class="form-group">
+            			   		<label for="nameField">{{'PROPERTY.EXECUTIONLISTENERS.FIELDS.NAME' | translate}}</label>
+            			   		<input type="text" id="nameField" class="form-control" ng-model="selectedFields[0].name" placeholder="{{'PROPERTY.EXECUTIONLISTENERS.FIELDS.NAME.PLACEHOLDER' | translate}}" />
+            				</div>
+                            <div class="form-group">
+            			   		<label for="stringValueField">{{'PROPERTY.EXECUTIONLISTENERS.FIELDS.STRINGVALUE' | translate}}</label>
+            			   		<input type="text" id="stringValueField" class="form-control" ng-model="selectedFields[0].stringValue" ng-change="fieldDetailsChanged()" placeholder="{{'PROPERTY.EXECUTIONLISTENERS.FIELDS.STRINGVALUE.PLACEHOLDER' | translate}}" />
+            				</div>
+            				<div class="form-group">
+            			   		<label for="expressionField">{{'PROPERTY.EXECUTIONLISTENERS.FIELDS.EXPRESSION' | translate}}</label>
+            			   		<input type="text" id="expressionField" class="form-control" ng-model="selectedFields[0].expression" ng-change="fieldDetailsChanged()" placeholder="{{'PROPERTY.EXECUTIONLISTENERS.FIELDS.EXPRESSION.PLACEHOLDER' | translate}}" />
+            				</div>
+            				<div class="form-group">
+            			   		<label for="stringField">{{'PROPERTY.EXECUTIONLISTENERS.FIELDS.STRING' | translate}}</label>
+            			   		<textarea id="stringField" class="form-control" ng-model="selectedFields[0].string" ng-change="fieldDetailsChanged()" placeholder="{{'PROPERTY.EXECUTIONLISTENERS.FIELDS.STRING.PLACEHOLDER' | translate}}"></textarea>
+            				</div>
+                            
+                        </div>
+                        <div ng-show="selectedFields.length == 0" class="muted no-property-selected"translate>PROPERTY.EXECUTIONLISTENERS.FIELDS.EMPTY</div>
+                    </div>
+                </div>
+            
+            </div>
+            <div class="modal-footer">
+                <button ng-click="cancel()" class="btn btn-primary" translate>ACTION.CANCEL</button>
+                <button ng-click="save()" class="btn btn-primary" translate>ACTION.SAVE</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 4 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/execution-listeners-write-template.html

@@ -0,0 +1,4 @@
+
+<!-- Just need to instantiate the controller, and it will take care of showing the modal dialog -->
+<span ng-controller="KisBpmExecutionListenersCtrl">
+</span>

+ 17 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/feedback-popup.html

@@ -0,0 +1,17 @@
+
+<div class="modal" ng-controller="BpmnFeedbackPopupCtrl">
+    <div class="modal-dialog">
+        <div class="modal-content">
+			<div class="modal-header">
+            	<h2>{{'PROPERTY.FEEDBACK.TITLE' | translate:property}}</h2>
+            </div>
+            <div class="modal-body">
+            	<p><textarea auto-focus class="form-control" ng-model="model.feedback" style="width:90%; height:100%; max-width: 100%; max-height: 100%; min-height: 300px"/></p>
+           	</div>
+           	<div class="modal-footer">
+            	<button ng-click="cancel()" class="btn btn-primary" translate >ACTION.CANCEL</button>
+              	<button ng-click="send()" ng-disabled="model.feedback.length === 0" class="btn btn-primary" translate >ACTION.SEND</button>
+            </div>
+		</div>
+	</div>
+</div>

+ 3 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/fields-display-template.html

@@ -0,0 +1,3 @@
+
+<span ng-if="!property.noValue">{{'PROPERTY.FIELDS' | translate:property.value.fields}}</span>
+<span ng-if="property.noValue">{{'PROPERTY.FIELDS.EMPTY' | translate}}</span>

+ 61 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/fields-popup.html

@@ -0,0 +1,61 @@
+
+<div class="modal" ng-controller="KisBpmFieldsPopupCtrl">
+    <div class="modal-dialog modal-wide">
+        <div class="modal-content">
+			<div class="modal-header">
+			    <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">&times;</button>
+			    <h3>{{'PROPERTY.PROPERTY.EDIT.TITLE' | translate:property}}</h3>
+			</div>
+			<div class="modal-body">
+
+			    <div class="row row-no-gutter">
+			        <div class="col-xs-6">
+                        <div ng-if="translationsRetrieved" class="kis-listener-grid" ng-grid="gridOptions"></div>
+			            <div class="pull-right">
+			                <div class="btn-group">
+			                    <a href="#" class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.MOVE.UP' | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveFieldUp()"><i class="glyphicon glyphicon-arrow-up"></i></a>
+			                    <a href="#" class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.MOVE.DOWN' | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveFieldDown()"><i class="glyphicon glyphicon-arrow-down"></i></a>
+			                </div>
+			                <div class="btn-group">
+			                    <a href="#" class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.ADD' | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="addNewField()"><i class="glyphicon glyphicon-plus"></i></a>
+			                    <a href="#" class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.REMOVE' | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="removeField()"><i class="glyphicon glyphicon-minus"></i></a>
+			                </div>
+			            </div>
+			        </div>
+
+			       <div class="col-xs-6">
+			            <div ng-show="selectedFields.length > 0">
+
+			                <div class="form-group">
+                                <label for="fieldName">{{'PROPERTY.FIELDS.NAME' | translate}}</label>
+                                <input type="text" id="fieldName"  class="form-control" ng-model="selectedFields[0].name" placeholder="{{'PROPERTY.FIELDS.NAME.PLACEHOLDER' | translate}}" />
+                            </div>
+
+                            <div class="form-group">
+                            <label for="fieldStringValue">{{'PROPERTY.FIELDS.STRINGVALUE' | translate}}</label>
+                                <input type="text" id="fieldStringValue"  class="form-control" ng-model="selectedFields[0].stringValue" ng-change="fieldDetailsChanged()" placeholder="{{'PROPERTY.FIELDS.STRINGVALUE.PLACEHOLDER' | translate}}" />
+                            </div>
+
+                            <div class="form-group">
+                            <label for="fieldExpression">{{'PROPERTY.FIELDS.EXPRESSION' | translate}}</label>
+                                <input type="text" id="fieldExpression"  class="form-control" ng-model="selectedFields[0].expression" ng-change="fieldDetailsChanged()" placeholder="{{'PROPERTY.FIELDS.EXPRESSION.PLACEHOLDER' | translate}}" />
+                            </div>
+
+                            <div class="form-group">
+                            <label for="fieldString">{{'PROPERTY.FIELDS.STRING' | translate}}</label>
+			                        <textarea type="text" id="fieldString"  class="form-control" ng-model="selectedFields[0].string" ng-change="fieldDetailsChanged()" placeholder="{{'PROPERTY.FIELDS.STRING.PLACEHOLDER' | translate}}"></textarea>
+                            </div>
+
+			            </div>
+			            <div ng-show="selectedFields.length == 0" class="muted no-property-selected" translate>PROPERTY.FIELDS.EMPTY</div>
+			        </div>
+			    </div>
+			
+			</div>
+			<div class="modal-footer">
+			    <button ng-click="cancel()" class="btn btn-primary" translate>ACTION.CANCEL</button>
+			    <button ng-click="save()" class="btn btn-primary" translate>ACTION.SAVE</button>
+			</div>
+		</div>
+	</div>
+</div>

+ 4 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/fields-write-template.html

@@ -0,0 +1,4 @@
+
+<!-- Just need to instantiate the controller, and it will take care of showing the modal dialog -->
+<span ng-controller="KisBpmFieldsCtrl">
+</span>

+ 3 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/form-properties-display-template.html

@@ -0,0 +1,3 @@
+
+<span ng-if="!property.noValue">{{'PROPERTY.FORMPROPERTIES.VALUE' | translate:property.value.formProperties}}</span>
+<span ng-if="property.noValue" translate>PROPERTY.FORMPROPERTIES.EMPTY</span>

+ 117 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/form-properties-popup.html

@@ -0,0 +1,117 @@
+
+<div class="modal" ng-controller="KisBpmFormPropertiesPopupCtrl">
+    <div class="modal-dialog modal-wide">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">&times;</button>
+                <h2>{{'PROPERTY.PROPERTY.EDIT.TITLE' | translate:property}}</h2>
+            </div>
+            <div class="modal-body">
+            
+                <div class="row row-no-gutter">
+                    <div class="col-xs-6">
+                        <div ng-if="translationsRetrieved" class="default-grid" ng-grid="gridOptions"></div>
+                        <div class="pull-right">
+                            <div class="btn-group">
+                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.MOVE.UP' | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="movePropertyUp()"><i class="glyphicon glyphicon-arrow-up"></i></a>
+                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.MOVE.DOWN' | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="movePropertyDown()"><i class="glyphicon glyphicon-arrow-down"></i></a>
+                            </div>
+                            <div class="btn-group">
+                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.ADD' | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="addNewProperty()"><i class="glyphicon glyphicon-plus"></i></a>
+                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.REMOVE' | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="removeProperty()"><i class="glyphicon glyphicon-minus"></i></a>
+                            </div>
+                        </div>
+                    </div>
+            
+                    <div class="col-xs-6">
+                        <div ng-show="selectedProperties.length > 0">
+            
+                            <div class="form-group">
+            			   		<label for="idField">{{'PROPERTY.FORMPROPERTIES.ID' | translate}}</label>
+            			   		<input id="idField" class="form-control" type="text" ng-model="selectedProperties[0].id" placeholder="{{'PROPERTY.FORMPROPERTIES.ID.PLACEHOLDER' | translate }}" />
+            				</div>
+            				<div class="form-group">
+            			   		<label for="nameField">{{'PROPERTY.FORMPROPERTIES.NAME' | translate}}</label>
+            			   		<input id="nameField" class="form-control" type="text" ng-model="selectedProperties[0].name" placeholder="{{'PROPERTY.FORMPROPERTIES.NAME.PLACEHOLDER' | translate }}" />
+            				</div>
+            				<div class="form-group">
+            			   		<label for="typeField">{{'PROPERTY.FORMPROPERTIES.TYPE' | translate}}</label>
+            			   		<select id="typeField" class="form-control" ng-model="selectedProperties[0].type" ng-change="propertyTypeChanged()">
+                                    <option>string</option>
+                                    <option>long</option>
+                                    <option>boolean</option>
+                                    <option>date</option>
+                                    <option>enum</option>
+                                </select>
+            				</div>
+                           	<div class="form-group" ng-show="selectedProperties[0].datePattern">
+            			   		<label for="datePatternField">{{'PROPERTY.FORMPROPERTIES.DATEPATTERN' | translate}}</label>
+            			   		<input id="datePatternField" class="form-control" type="text" ng-model="selectedProperties[0].datePattern" placeholder="{{'PROPERTY.FORMPROPERTIES.DATEPATTERN.PLACEHOLDER' | translate }}" />
+            				</div>
+                            <div ng-if="selectedProperties[0].type == 'enum'" style="padding-bottom:10px">
+            			   		<div class="row row-no-gutter">
+				                    <div class="col-xs-6">
+				                        <div ng-if="translationsRetrieved" class="kis-listener-grid" ng-grid="enumGridOptions"></div>
+				                        <div class="pull-right">
+				                            <div class="btn-group">
+				                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.UP | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveEnumValueUp()"><i class="glyphicon glyphicon-arrow-up"></i></a>
+				                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.DOWN | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveEnumValueDown()"><i class="glyphicon glyphicon-arrow-down"></i></a>
+				                            </div>
+				                            <div class="btn-group">
+				                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.ADD | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="addNewEnumValue()"><i class="glyphicon glyphicon-plus"></i></a>
+				                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.REMOVE | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="removeEnumValue()"><i class="glyphicon glyphicon-minus"></i></a>
+				                            </div>
+				                        </div>
+				                    </div>
+				            
+				                    <div class="col-xs-6">
+				                        <div ng-show="selectedEnumValues.length > 0">
+				            
+				                            <div class="form-group">
+				            			   		<label for="classField">{{'PROPERTY.FORMPROPERTIES.VALUES.ID' | translate}}</label>
+				            			   		<input type="text" id="classField" class="form-control" ng-model="selectedEnumValues[0].id" placeholder="{{'PROPERTY.FORMPROPERTIES.VALUES.ID.PLACEHOLDER' | translate}}" />
+				            				</div>
+				            				<div class="form-group">
+				            			   		<label for="classField">{{'PROPERTY.FORMPROPERTIES.VALUES.NAME' | translate}}</label>
+				            			   		<input type="text" id="classField" class="form-control" ng-model="selectedEnumValues[0].name" placeholder="{{'PROPERTY.FORMPROPERTIES.VALUES.NAME.PLACEHOLDER' | translate}}" />
+				            				</div>
+				                        </div>
+				                        <div ng-show="selectedEnumValues.length == 0" class="muted no-property-selected" translate>PROPERTY.FORMPROPERTIES.ENUMVALUES.EMPTY</div>
+				                    </div>
+				                </div>
+            				</div>
+                            <div class="form-group">
+            			   		<label for="expressionField">{{'PROPERTY.FORMPROPERTIES.EXPRESSION' | translate}}</label>
+            			   		<input id="expressionField" class="form-control" type="text" ng-model="selectedProperties[0].expression" placeholder="{{'PROPERTY.FORMPROPERTIES.EXPRESSION.PLACEHOLDER' | translate }}" />
+            				</div>
+            				<div class="form-group">
+            			   		<label for="variableField">{{'PROPERTY.FORMPROPERTIES.VARIABLE' | translate}}</label>
+            			   		<input id="variableField" class="form-control" type="text" ng-model="selectedProperties[0].variable" placeholder="{{'PROPERTY.FORMPROPERTIES.VARIABLE.PLACEHOLDER' | translate }}" />
+            				</div>
+            				<div class="form-inline">
+            					<div class="form-group col-xs-2" >
+            				   		<label for="requiredField">{{'PROPERTY.FORMPROPERTIES.REQUIRED' | translate}}</label>
+            				   		<input id="requiredField" class="form-control" type="checkbox" ng-model="selectedProperties[0].required" />
+            				   	</div>
+            				   	<div class="form-group col-xs-2">
+            				   		<label for="readableField">{{'PROPERTY.FORMPROPERTIES.READABLE' | translate}}</label>
+            				   		<input id="readableField" class="form-control" type="checkbox" ng-model="selectedProperties[0].readable" />
+            					</div>
+            					<div class="form-group col-xs-2">
+            						<label for="writableField">{{'PROPERTY.FORMPROPERTIES.WRITABLE' | translate}}</label>
+            			   			<input id="writableField" class="form-control" type="checkbox" ng-model="selectedProperties[0].writable" />
+            					</div>
+            				</div>
+                        </div>
+                        <div ng-show="selectedProperties.length == 0" class="muted no-property-selected" translate>PROPERTY.FORMPROPERTIES.EMPTY</div>
+                    </div>
+                </div>
+            
+            </div>
+            <div class="modal-footer">
+                <button ng-click="cancel()" class="btn btn-primary" translate>ACTION.CANCEL</button>
+                <button ng-click="save()" class="btn btn-primary" translate>ACTION.SAVE</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 4 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/form-properties-write-template.html

@@ -0,0 +1,4 @@
+
+<!-- Just need to instantiate the controller, and it will take care of showing the modal dialog -->
+<span ng-controller="KisBpmFormPropertiesCtrl">
+</span>

+ 3 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/in-parameters-display-template.html

@@ -0,0 +1,3 @@
+
+<span ng-if="!property.noValue">{{'PROPERTY.INPARAMETERS.VALUE' | translate:property.value.inParameters}}</span>
+<span ng-if="property.noValue" translate>PROPERTY.INPARAMETERS.EMPTY</span>

+ 53 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/in-parameters-popup.html

@@ -0,0 +1,53 @@
+
+<div class="modal" ng-controller="KisBpmInParametersPopupCtrl">
+    <div class="modal-dialog modal-wide">
+        <div class="modal-content">
+			<div class="modal-header">
+			    <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">&times;</button>
+			    <h2>{{'PROPERTY.PROPERTY.EDIT.TITLE' | translate:property}}</h2>
+			</div>
+			<div class="modal-body">
+			
+			    <div class="row row-no-gutter">
+			        <div class="col-xs-6">
+			            <div ng-if="translationsRetrieved" class="kis-listener-grid" ng-grid="gridOptions"></div>
+			            <div class="pull-right">
+			                <div class="btn-group">
+			                    <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.MOVE.UP' | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveParameterUp()"><i class="glyphicon glyphicon-arrow-up"></i></a>
+			                    <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.MOVE.DOWN' | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveParameterDown()"><i class="glyphicon glyphicon-arrow-down"></i></a>
+			                </div>
+			                <div class="btn-group">
+			                    <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.ADD' | translate:property}}" data-placement="bottom" data-original-title="" title="" ng-click="addNewParameter()"><i class="glyphicon glyphicon-plus"></i></a>
+			                    <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.REMOVE' | translate:property}}" data-placement="bottom" data-original-title="" title="" ng-click="removeParameter()"><i class="glyphicon glyphicon-minus"></i></a>
+			                </div>
+			            </div>
+			        </div>
+			
+			        <div class="col-xs-6">
+			            <div ng-show="selectedParameters.length > 0">
+							
+							<div class="form-group">
+						   		<label for="sourceField">{{'PROPERTY.PARAMETER.SOURCE' | translate}}</label>
+						   		<input type="text" id="sourceField" class="form-control" ng-model="selectedParameters[0].source" placeholder="{{'PROPERTY.PARAMETER.SOURCE.PLACEHOLDER' | translate}}" />
+							</div>
+							<div class="form-group">
+						   		<label for="expressionField">{{'PROPERTY.PARAMETER.SOURCEEXPRESSION' | translate}}</label>
+						   		<input type="text" id="expressionField" class="form-control" ng-model="selectedParameters[0].sourceExpression" placeholder="{{'PROPERTY.PARAMETER.SOURCEEXPRESSION.PLACEHOLDER' | translate}}" />
+							</div>
+							<div class="form-group">
+						   		<label for="expressionField">{{'PROPERTY.PARAMETER.TARGET' | translate}}</label>
+						   		<input type="text" id="expressionField" class="form-control" ng-model="selectedParameters[0].target" placeholder="{{'PROPERTY.PARAMETER.TARGET.PLACEHOLDER' | translate}}" />
+							</div>
+			                
+			            </div>
+			            <div ng-show="selectedParameters.length == 0" class="muted no-property-selected" translate>PROPERTY.PARAMETER.EMPTY</div>
+			        </div>
+			    </div>
+			</div>
+			<div class="modal-footer">
+			    <button ng-click="cancel()" class="btn btn-primary" translate>ACTION.CANCEL</button>
+			    <button ng-click="save()" class="btn btn-primary" translate>ACTION.SAVE</button>
+			</div>
+		</div>
+	</div>
+</div>

+ 4 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/in-parameters-write-template.html

@@ -0,0 +1,4 @@
+
+<!-- Just need to instantiate the controller, and it will take care of showing the modal dialog -->
+<span ng-controller="KisBpmInParametersCtrl">
+</span>

+ 2 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/message-definitions-display-template.html

@@ -0,0 +1,2 @@
+<span ng-if="!property.noValue">{{'PROPERTY.MESSAGEDEFINITIONS.DISPLAY' | translate:property.value}}</span>
+<span ng-if="property.noValue" translate>PROPERTY.MESSAGEDEFINITIONS.EMPTY</span>

+ 50 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/message-definitions-popup.html

@@ -0,0 +1,50 @@
+
+<div class="modal" ng-controller="ActivitiMessageDefinitionsPopupCtrl">
+    <div class="modal-dialog modal-wide">
+        <div class="modal-content">
+
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">&times;</button>
+                <h2>{{'PROPERTY.PROPERTY.EDIT.TITLE' | translate:property}}</h2>
+            </div>
+
+            <div class="modal-body">
+            
+                <div class="row row-no-gutter">
+
+                	<div class="col-xs-8">
+                        <div ng-if="translationsRetrieved" class="kis-listener-grid" ng-grid="gridOptions"></div>
+            	        <div class="pull-right">
+            	            <div class="btn-group">
+            	                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.ADD | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="addNewMessageDefinition()"><i class="glyphicon glyphicon-plus"></i></a>
+            	                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.REMOVE | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="removeMessageDefinition()"><i class="glyphicon glyphicon-minus"></i></a>
+            	            </div>
+            	        </div>
+            		</div>
+
+                    <div class="col-xs-4" ng-show="selectedMessages && selectedMessages.length > 0">
+
+                        <div class="form-group">
+                            <label>{{'PROPERTY.MESSAGEDEFINITIONS.ID' | translate}}</label>
+                            <input type="text" class="form-control" ng-model="selectedMessages[0].id">
+                        </div>
+
+                        <div class="form-group">
+                            <label>{{'PROPERTY.MESSAGEDEFINITIONS.NAME' | translate}}</label>
+                            <input type="text" class="form-control" ng-model="selectedMessages[0].name">
+                        </div>
+
+                    </div>
+
+            	</div>
+            	
+            </div>
+
+            <div class="modal-footer">
+                <button ng-click="cancel()" class="btn btn-primary" translate>ACTION.CANCEL</button>
+                <button ng-click="save()" class="btn btn-primary" translate>ACTION.SAVE</button>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 3 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/message-definitions-write-template.html

@@ -0,0 +1,3 @@
+<!-- Just need to instantiate the controller, and it will take care of showing the modal dialog -->
+<span ng-controller="ActivitiMessageDefinitionsCtrl">
+</span>

+ 4 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/message-property-write-template.html

@@ -0,0 +1,4 @@
+<div ng-controller="ActivitiMessageRefCtrl">
+    <select ng-model="property.value" ng-change="messageChanged()" ng-options="messageDefinition.id as (messageDefinition.name + ' (' + messageDefinition.id + ')') for messageDefinition in messageDefinitions">
+    </select>
+</div>

+ 8 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/multiinstance-property-write-template.html

@@ -0,0 +1,8 @@
+
+<div ng-controller="KisBpmMultiInstanceCtrl">
+    <select ng-model="property.value" ng-change="multiInstanceChanged()">
+    	<option>None</option>
+    	<option>Parallel</option>
+    	<option>Sequential</option>
+    </select>
+</div>

+ 3 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/out-parameters-display-template.html

@@ -0,0 +1,3 @@
+
+<span ng-if="!property.noValue">{{'PROPERTY.OUTPARAMETERS.VALUE' | translate:property.value.outParameters}}</span>
+<span ng-if="property.noValue" translate>PROPERTY.OUTPARAMETERS.EMPTY</span>

+ 53 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/out-parameters-popup.html

@@ -0,0 +1,53 @@
+
+<div class="modal" ng-controller="KisBpmOutParametersPopupCtrl">
+    <div class="modal-dialog modal-wide">
+        <div class="modal-content">
+			<div class="modal-header">
+			    <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">&times;</button>
+			    <h2>{{'PROPERTY.PROPERTY.EDIT.TITLE' | translate:property}}</h2>
+			</div>
+			<div class="modal-body">
+			
+			    <div class="row row-no-gutter">
+			        <div class="col-xs-6">
+			            <div ng-if="translationsRetrieved" class="kis-listener-grid" ng-grid="gridOptions"></div>
+			            <div class="pull-right">
+			                <div class="btn-group">
+			                    <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.MOVE.UP' | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveParameterUp()"><i class="glyphicon glyphicon-arrow-up"></i></a>
+			                    <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.MOVE.DOWN' | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveParameterDown()"><i class="glyphicon glyphicon-arrow-down"></i></a>
+			                </div>
+			                <div class="btn-group">
+			                    <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.ADD' | translate:property}}" data-placement="bottom" data-original-title="" title="" ng-click="addNewParameter()"><i class="glyphicon glyphicon-plus"></i></a>
+			                    <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{'ACTION.REMOVE' | translate:property}}" data-placement="bottom" data-original-title="" title="" ng-click="removeParameter()"><i class="glyphicon glyphicon-minus"></i></a>
+			                </div>
+			            </div>
+			        </div>
+			
+			        <div class="col-xs-6">
+			            <div ng-show="selectedParameters.length > 0">
+							
+							<div class="form-group">
+						   		<label for="sourceField">{{'PROPERTY.PARAMETER.SOURCE' | translate}}</label>
+						   		<input type="text" id="sourceField" class="form-control" ng-model="selectedParameters[0].source" placeholder="{{'PROPERTY.PARAMETER.SOURCE.PLACEHOLDER' | translate}}" />
+							</div>
+							<div class="form-group">
+						   		<label for="expressionField">{{'PROPERTY.PARAMETER.SOURCEEXPRESSION' | translate}}</label>
+						   		<input type="text" id="expressionField" class="form-control" ng-model="selectedParameters[0].sourceExpression" placeholder="{{'PROPERTY.PARAMETER.SOURCEEXPRESSION.PLACEHOLDER' | translate}}" />
+							</div>
+							<div class="form-group">
+						   		<label for="expressionField">{{'PROPERTY.PARAMETER.TARGET' | translate}}</label>
+						   		<input type="text" id="expressionField" class="form-control" ng-model="selectedParameters[0].target" placeholder="{{'PROPERTY.PARAMETER.TARGET.PLACEHOLDER' | translate}}" />
+							</div>
+			                
+			            </div>
+			            <div ng-show="selectedParameters.length == 0" class="muted no-property-selected" translate>PROPERTY.PARAMETER.EMPTY</div>
+			        </div>
+			    </div>
+			</div>
+			<div class="modal-footer">
+			    <button ng-click="cancel()" class="btn btn-primary" translate>ACTION.CANCEL</button>
+			    <button ng-click="save()" class="btn btn-primary" translate>ACTION.SAVE</button>
+			</div>
+		</div>
+	</div>
+</div>

+ 4 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/out-parameters-write-template.html

@@ -0,0 +1,4 @@
+
+<!-- Just need to instantiate the controller, and it will take care of showing the modal dialog -->
+<span ng-controller="KisBpmOutParametersCtrl">
+</span>

+ 3 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/sequenceflow-order-display-template.html

@@ -0,0 +1,3 @@
+
+<span ng-if="!property.noValue" translate>PROPERTY.SEQUENCEFLOW.ORDER.NOT.EMPTY</span>
+<span ng-if="property.noValue" translate>PROPERTY.SEQUENCEFLOW.ORDER.EMPTY</span>

+ 47 - 0
eladmin-activity/src/main/resources/static/editor-app/configuration/properties/sequenceflow-order-popup.html

@@ -0,0 +1,47 @@
+
+<div class="modal" ng-controller="KisBpmSequenceFlowOrderPopupCtrl">
+    <div class="modal-dialog">
+        <div class="modal-content">
+			<div class="modal-header">
+			    <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">&times;</button>
+			    <h3>{{'PROPERTY.PROPERTY.EDIT.TITLE' | translate:property}}</h3>
+			</div>
+			
+			<div class="modal-body">
+			
+			    <div translate>PROPERTY.SEQUENCEFLOW.ORDER.DESCRIPTION</div>
+			    <br/>
+			    <ol>
+			        <li class="sequence-flow-order-element" ng-repeat="sequenceFlow in outgoingSequenceFlow">
+			            {{'PROPERTY.SEQUENCEFLOW.ORDER.SEQUENCEFLOW.VALUE' | translate:sequenceFlow}}
+			            <a class="btn btn-icon btn-sm"
+			               rel="tooltip"
+			               data-title="{{'ACTION.MOVE.UP' | translate}}"
+			               data-placement="bottom"
+			               data-original-title="" title=""
+			               ng-click="moveUp($index)"
+			               ng-if="$index > 0">
+			                 <i class="glyphicon glyphicon-arrow-up"></i>
+			            </a>
+			            <a class="btn btn-icon btn-sm"
+			               rel="tooltip"
+			               data-title="{{'ACTION.MOVE.DOWN' | translate}}"
+			               data-placement="bottom"
+			               data-original-title=""
+			               title=""
+			               ng-click="moveDown($index)"
+			               ng-if="$index < outgoingSequenceFlow.length - 1">
+			                 <i class="glyphicon glyphicon-arrow-down"></i>
+			            </a>
+			        </li>
+			    </ol>
+			
+			
+			</div>
+			<div class="modal-footer">
+			    <button ng-click="cancel()" class="btn btn-primary" translate>ACTION.CANCEL</button>
+			    <button ng-click="save()" class="btn btn-primary" translate>ACTION.SAVE</button>
+			</div>
+		</div>
+	</div>
+</div>

Some files were not shown because too many files changed in this diff