summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjose.lausuch <jose.lausuch@ericsson.com>2016-06-01 23:00:08 +0200
committerjose.lausuch <jose.lausuch@ericsson.com>2016-06-02 20:12:49 +0200
commitaf427a992a96519c00ac9f98db8745f9fc1198fb (patch)
tree36f69164b289ce2405419f3946b7af0ba8f8b08e
parent43b44d05ed662d7e9e4f0a66b5f1744685d7b5cc (diff)
OPNFV Infra Dashboard
JIRA: RELENG-12 Change-Id: I7451a3d234e4e5d946cdb905d5720be6159b6544 Signed-off-by: jose.lausuch <jose.lausuch@ericsson.com>
-rw-r--r--tools/infra-dashboard/README.md3
-rw-r--r--tools/infra-dashboard/css/bootstrap.min.css5488
-rw-r--r--tools/infra-dashboard/css/dataTables.bootstrap.min.css1
-rw-r--r--tools/infra-dashboard/css/font-awesome.css1801
-rw-r--r--tools/infra-dashboard/css/fullcalendar.css1260
-rw-r--r--tools/infra-dashboard/css/fullcalendar.print.css208
-rw-r--r--tools/infra-dashboard/css/highslide.min.css793
-rw-r--r--tools/infra-dashboard/css/opnfv.css2479
-rw-r--r--tools/infra-dashboard/css/source-sans-pro.css96
-rw-r--r--tools/infra-dashboard/css/template.css802
-rw-r--r--tools/infra-dashboard/css/theme.css387
-rw-r--r--tools/infra-dashboard/fonts/SourceSansPro-Bold.ttfbin0 -> 118604 bytes
-rw-r--r--tools/infra-dashboard/fonts/SourceSansPro-ExtraLight.ttfbin0 -> 119324 bytes
-rw-r--r--tools/infra-dashboard/fonts/SourceSansPro-Regular.ttfbin0 -> 119080 bytes
-rw-r--r--tools/infra-dashboard/fonts/fontawesome-webfont.ttfbin0 -> 142072 bytes
-rw-r--r--tools/infra-dashboard/fonts/fontawesome-webfont.woffbin0 -> 83588 bytes
-rw-r--r--tools/infra-dashboard/fonts/fontawesome-webfont.woff2bin0 -> 66624 bytes
-rw-r--r--tools/infra-dashboard/fonts/source-sans-pro.black.ttfbin0 -> 118004 bytes
-rw-r--r--tools/infra-dashboard/index.php353
-rw-r--r--tools/infra-dashboard/js/bootstrap.js2118
-rw-r--r--tools/infra-dashboard/js/fullcalendar.js12670
-rw-r--r--tools/infra-dashboard/js/fullcalendar.min.js9
-rw-r--r--tools/infra-dashboard/js/highslide-full.min.js3315
-rw-r--r--tools/infra-dashboard/js/highslide.config.min.js1
-rw-r--r--tools/infra-dashboard/js/jquery-1.7.2.js9404
-rw-r--r--tools/infra-dashboard/js/jquery.min.js5
-rw-r--r--tools/infra-dashboard/js/modernizr.js4
-rw-r--r--tools/infra-dashboard/js/moment.min.js7
-rw-r--r--tools/infra-dashboard/js/script.js27
-rw-r--r--tools/infra-dashboard/js/test_graph.js108
-rw-r--r--tools/infra-dashboard/media/ajax-loader.gifbin0 -> 3208 bytes
-rw-r--r--tools/infra-dashboard/media/collaborative-projects-logo.pngbin0 -> 3625 bytes
-rw-r--r--tools/infra-dashboard/media/diagonal-white.pngbin0 -> 82 bytes
-rw-r--r--tools/infra-dashboard/media/favicon_400x400.jpgbin0 -> 10235 bytes
-rw-r--r--tools/infra-dashboard/media/loader.white.gifbin0 -> 673 bytes
-rw-r--r--tools/infra-dashboard/media/opnfvlogo.JPGbin0 -> 12297 bytes
-rw-r--r--tools/infra-dashboard/pages/ci_pods.php91
-rw-r--r--tools/infra-dashboard/pages/dev_pods.php342
-rw-r--r--tools/infra-dashboard/pages/slaves.php60
-rw-r--r--tools/infra-dashboard/populateDB.txt203
-rw-r--r--tools/infra-dashboard/utils/book.php82
-rw-r--r--tools/infra-dashboard/utils/database.php18
-rw-r--r--tools/infra-dashboard/utils/jenkinsAdapter.php204
-rw-r--r--tools/infra-dashboard/utils/login.php56
44 files changed, 42395 insertions, 0 deletions
diff --git a/tools/infra-dashboard/README.md b/tools/infra-dashboard/README.md
new file mode 100644
index 00000000..b43afd66
--- /dev/null
+++ b/tools/infra-dashboard/README.md
@@ -0,0 +1,3 @@
+# OPNFV Infrastructure Dashboard
+This is the dashboard proposal for OPNFV Pharos and CI resources reservation.
+Creator and maintainer: jose.lausuch@ericsson.com
diff --git a/tools/infra-dashboard/css/bootstrap.min.css b/tools/infra-dashboard/css/bootstrap.min.css
new file mode 100644
index 00000000..1d23653c
--- /dev/null
+++ b/tools/infra-dashboard/css/bootstrap.min.css
@@ -0,0 +1,5488 @@
+/*!
+ * Bootstrap v3.2.0 (http://getbootstrap.com)
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+/*!
+ * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=e82eeb1f3b04b506268e)
+ * Config saved to config.json and https://gist.github.com/e82eeb1f3b04b506268e
+ */
+/*! normalize.css v3.0.1 | MIT License | git.io/normalize */
+
+.sr-only,
+svg:not(:root) {
+ overflow: hidden
+}
+hr,
+img {
+ border: 0
+}
+body,
+figure {
+ margin: 0
+}
+.img-thumbnail,
+.thumbnail {
+ -webkit-transition: all .2s ease-in-out
+}
+.btn-group>.btn-group,
+.btn-toolbar .btn-group,
+.btn-toolbar .input-group,
+.col-xs-1,
+.col-xs-10,
+.col-xs-11,
+.col-xs-12,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9,
+.dropdown-menu {
+ float: left
+}
+.navbar-fixed-bottom .navbar-collapse,
+.navbar-fixed-top .navbar-collapse,
+.pre-scrollable {
+ max-height: 340px
+}
+html {
+ font-family: sans-serif;
+ -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;
+ font-size: 10px;
+ -webkit-tap-highlight-color: transparent
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block
+}
+audio,
+canvas,
+progress,
+video {
+ display: inline-block;
+ vertical-align: baseline
+}
+audio:not([controls]) {
+ display: none;
+ height: 0
+}
+[hidden],
+template {
+ display: none
+}
+a {
+ background: 0 0
+}
+a:active,
+a:hover {
+ outline: 0
+}
+b,
+optgroup,
+strong {
+ font-weight: 700
+}
+dfn {
+ font-style: italic
+}
+h1 {
+ margin: .67em 0
+}
+mark {
+ background: #ff0;
+ color: #000
+}
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline
+}
+sup {
+ top: -.5em
+}
+sub {
+ bottom: -.25em
+}
+img {
+ vertical-align: middle
+}
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0
+}
+pre,
+textarea {
+ overflow: auto
+}
+code,
+kbd,
+pre,
+samp {
+ font-size: 1em
+}
+button,
+input,
+optgroup,
+select,
+textarea {
+ color: inherit;
+ font: inherit;
+ margin: 0
+}
+button {
+ overflow: visible
+}
+button,
+select {
+ text-transform: none
+}
+button,
+html input[type=button],
+input[type=reset],
+input[type=submit] {
+ -webkit-appearance: button;
+ cursor: pointer
+}
+button[disabled],
+html input[disabled] {
+ cursor: default
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0
+}
+input[type=checkbox],
+input[type=radio] {
+ box-sizing: border-box;
+ padding: 0
+}
+input[type=number]::-webkit-inner-spin-button,
+input[type=number]::-webkit-outer-spin-button {
+ height: auto
+}
+input[type=search]::-webkit-search-cancel-button,
+input[type=search]::-webkit-search-decoration {
+ -webkit-appearance: none
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0
+}
+td,
+th {
+ padding: 0
+}
+@media print {
+ blockquote,
+ img,
+ pre,
+ tr {
+ page-break-inside: avoid
+ }
+ * {
+ text-shadow: none!important;
+ color: #000!important;
+ background: 0 0!important;
+ box-shadow: none!important
+ }
+ a,
+ a:visited {
+ text-decoration: underline
+ }
+ a[href]:after {
+ content: " (" attr(href) ")"
+ }
+ abbr[title]:after {
+ content: " (" attr(title) ")"
+ }
+ a[href^="#"]:after,
+ a[href^="javascript:"]:after {
+ content: ""
+ }
+ blockquote,
+ pre {
+ border: 1px solid #999
+ }
+ thead {
+ display: table-header-group
+ }
+ img {
+ max-width: 100%!important
+ }
+ h2,
+ h3,
+ p {
+ orphans: 3;
+ widows: 3
+ }
+ h2,
+ h3 {
+ page-break-after: avoid
+ }
+ select {
+ background: #fff!important
+ }
+ .navbar {
+ display: none
+ }
+ .table td,
+ .table th {
+ background-color: #fff!important
+ }
+ .btn>.caret,
+ .dropup>.btn>.caret {
+ border-top-color: #000!important
+ }
+ .label {
+ border: 1px solid #000
+ }
+ .table {
+ border-collapse: collapse!important
+ }
+ .table-bordered td,
+ .table-bordered th {
+ border: 1px solid #ddd!important
+ }
+}
+.btn,
+.btn-danger.active,
+.btn-danger:active,
+.btn-default.active,
+.btn-default:active,
+.btn-info.active,
+.btn-info:active,
+.btn-primary.active,
+.btn-primary:active,
+.btn-warning.active,
+.btn-warning:active,
+.btn.active,
+.btn:active,
+.dropdown-menu>.disabled>a:focus,
+.dropdown-menu>.disabled>a:hover,
+.form-control,
+.navbar-toggle,
+.open>.dropdown-toggle.btn-danger,
+.open>.dropdown-toggle.btn-default,
+.open>.dropdown-toggle.btn-info,
+.open>.dropdown-toggle.btn-primary,
+.open>.dropdown-toggle.btn-warning {
+ background-image: none
+}
+.img-thumbnail,
+body {
+ background-color: #fff
+}
+*,
+:after,
+:before {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box
+}
+body {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #333
+}
+button,
+input,
+select,
+textarea {
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit
+}
+a {
+ color: #428bca;
+ text-decoration: none
+}
+a:focus,
+a:hover {
+ color: #2a6496;
+ text-decoration: underline
+}
+a:focus {
+ outline: dotted thin;
+ outline: -webkit-focus-ring-color auto 5px;
+ outline-offset: -2px
+}
+.carousel-inner>.item>a>img,
+.carousel-inner>.item>img,
+.img-responsive,
+.thumbnail a>img,
+.thumbnail>img {
+ display: block;
+ width: 100%\9;
+ max-width: 100%;
+ height: auto
+}
+.img-rounded {
+ border-radius: 6px
+}
+.img-thumbnail {
+ padding: 4px;
+ line-height: 1.42857143;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ -o-transition: all .2s ease-in-out;
+ transition: all .2s ease-in-out;
+ display: inline-block;
+ width: 100%\9;
+ max-width: 100%;
+ height: auto
+}
+.img-circle {
+ border-radius: 50%
+}
+hr {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ border-top: 1px solid #eee
+}
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ margin: -1px;
+ padding: 0;
+ clip: rect(0, 0, 0, 0);
+ border: 0
+}
+.sr-only-focusable:active,
+.sr-only-focusable:focus {
+ position: static;
+ width: auto;
+ height: auto;
+ margin: 0;
+ overflow: visible;
+ clip: auto
+}
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: inherit;
+ font-weight: 500;
+ line-height: 1.1;
+ color: inherit
+}
+.h1 .small,
+.h1 small,
+.h2 .small,
+.h2 small,
+.h3 .small,
+.h3 small,
+.h4 .small,
+.h4 small,
+.h5 .small,
+.h5 small,
+.h6 .small,
+.h6 small,
+h1 .small,
+h1 small,
+h2 .small,
+h2 small,
+h3 .small,
+h3 small,
+h4 .small,
+h4 small,
+h5 .small,
+h5 small,
+h6 .small,
+h6 small {
+ font-weight: 400;
+ line-height: 1;
+ color: #777
+}
+.h1,
+.h2,
+.h3,
+h1,
+h2,
+h3 {
+ margin-top: 20px;
+ margin-bottom: 10px
+}
+.h1 .small,
+.h1 small,
+.h2 .small,
+.h2 small,
+.h3 .small,
+.h3 small,
+h1 .small,
+h1 small,
+h2 .small,
+h2 small,
+h3 .small,
+h3 small {
+ font-size: 65%
+}
+.h4,
+.h5,
+.h6,
+h4,
+h5,
+h6 {
+ margin-top: 10px;
+ margin-bottom: 10px
+}
+.h4 .small,
+.h4 small,
+.h5 .small,
+.h5 small,
+.h6 .small,
+.h6 small,
+h4 .small,
+h4 small,
+h5 .small,
+h5 small,
+h6 .small,
+h6 small {
+ font-size: 75%
+}
+.h1,
+h1 {
+ font-size: 36px
+}
+.h2,
+h2 {
+ font-size: 30px
+}
+.h3,
+h3 {
+ font-size: 24px
+}
+.h4,
+h4 {
+ font-size: 18px
+}
+.h5,
+h5 {
+ font-size: 14px
+}
+.h6,
+h6 {
+ font-size: 9pt
+}
+p {
+ margin: 0 0 10px
+}
+.lead {
+ margin-bottom: 20px;
+ font-size: 1pc;
+ font-weight: 300;
+ line-height: 1.4
+}
+dt,
+label {
+ font-weight: 700
+}
+address,
+blockquote .small,
+blockquote footer,
+blockquote small,
+dd,
+dt,
+pre {
+ line-height: 1.42857143
+}
+@media (min-width: 768px) {
+ .lead {
+ font-size: 21px
+ }
+}
+.small,
+small {
+ font-size: 85%
+}
+cite {
+ font-style: normal
+}
+.mark,
+mark {
+ background-color: #fcf8e3;
+ padding: .2em
+}
+.list-inline,
+.list-unstyled {
+ padding-left: 0;
+ list-style: none
+}
+.text-left {
+ text-align: left
+}
+.text-right {
+ text-align: right
+}
+.text-center {
+ text-align: center
+}
+.text-justify {
+ text-align: justify
+}
+.text-nowrap {
+ white-space: nowrap
+}
+.text-lowercase {
+ text-transform: lowercase
+}
+.text-uppercase {
+ text-transform: uppercase
+}
+.text-capitalize {
+ text-transform: capitalize
+}
+.text-muted {
+ color: #777
+}
+.text-primary {
+ color: #428bca
+}
+a.text-primary:hover {
+ color: #3071a9
+}
+.text-success {
+ color: #3c763d
+}
+a.text-success:hover {
+ color: #2b542c
+}
+.text-info {
+ color: #31708f
+}
+a.text-info:hover {
+ color: #245269
+}
+.text-warning {
+ color: #8a6d3b
+}
+a.text-warning:hover {
+ color: #66512c
+}
+.text-danger {
+ color: #a94442
+}
+a.text-danger:hover {
+ color: #843534
+}
+.bg-primary {
+ color: #fff;
+ background-color: #428bca
+}
+a.bg-primary:hover {
+ background-color: #3071a9
+}
+.bg-success {
+ background-color: #dff0d8
+}
+a.bg-success:hover {
+ background-color: #c1e2b3
+}
+.bg-info {
+ background-color: #d9edf7
+}
+a.bg-info:hover {
+ background-color: #afd9ee
+}
+.bg-warning {
+ background-color: #fcf8e3
+}
+a.bg-warning:hover {
+ background-color: #f7ecb5
+}
+.bg-danger {
+ background-color: #f2dede
+}
+a.bg-danger:hover {
+ background-color: #e4b9b9
+}
+pre code,
+table {
+ background-color: transparent
+}
+.page-header {
+ padding-bottom: 9px;
+ margin: 40px 0 20px;
+ border-bottom: 1px solid #eee
+}
+dl,
+ol,
+ul {
+ margin-top: 0
+}
+blockquote ol:last-child,
+blockquote p:last-child,
+blockquote ul:last-child,
+ol ol,
+ol ul,
+ul ol,
+ul ul {
+ margin-bottom: 0
+}
+address,
+dl {
+ margin-bottom: 20px
+}
+ol,
+ul {
+ margin-bottom: 10px
+}
+.list-inline {
+ margin-left: -5px
+}
+.list-inline>li {
+ display: inline-block;
+ padding-left: 5px;
+ padding-right: 5px
+}
+dd {
+ margin-left: 0
+}
+@media (min-width: 768px) {
+ .dl-horizontal dt {
+ float: left;
+ width: 10pc;
+ clear: left;
+ text-align: right;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap
+ }
+ .dl-horizontal dd {
+ margin-left: 180px
+ }
+ .container {
+ width: 750px
+ }
+}
+abbr[data-original-title],
+abbr[title] {
+ cursor: help;
+ border-bottom: 1px dotted #777
+}
+.initialism {
+ font-size: 90%;
+ text-transform: uppercase
+}
+blockquote {
+ padding: 10px 20px;
+ margin: 0 0 20px;
+ font-size: 17.5px;
+ border-left: 5px solid #eee
+}
+blockquote .small,
+blockquote footer,
+blockquote small {
+ display: block;
+ font-size: 80%;
+ color: #777
+}
+legend,
+pre {
+ display: block;
+ color: #333
+}
+blockquote .small:before,
+blockquote footer:before,
+blockquote small:before {
+ content: '\2014 \00A0'
+}
+.blockquote-reverse,
+blockquote.pull-right {
+ padding-right: 15px;
+ padding-left: 0;
+ border-right: 5px solid #eee;
+ border-left: 0;
+ text-align: right
+}
+code,
+kbd {
+ padding: 2px 4px;
+ font-size: 90%
+}
+.blockquote-reverse .small:before,
+.blockquote-reverse footer:before,
+.blockquote-reverse small:before,
+blockquote.pull-right .small:before,
+blockquote.pull-right footer:before,
+blockquote.pull-right small:before {
+ content: ''
+}
+.blockquote-reverse .small:after,
+.blockquote-reverse footer:after,
+.blockquote-reverse small:after,
+blockquote.pull-right .small:after,
+blockquote.pull-right footer:after,
+blockquote.pull-right small:after {
+ content: '\00A0 \2014'
+}
+.popover>.arrow:after,
+blockquote:after,
+blockquote:before {
+ content: ""
+}
+address {
+ font-style: normal
+}
+code,
+kbd,
+pre,
+samp {
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace
+}
+code {
+ color: #c7254e;
+ background-color: #f9f2f4;
+ border-radius: 4px
+}
+kbd {
+ color: #fff;
+ background-color: #333;
+ border-radius: 3px;
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25)
+}
+kbd kbd {
+ padding: 0;
+ font-size: 100%;
+ box-shadow: none
+}
+pre {
+ padding: 9.5px;
+ margin: 0 0 10px;
+ font-size: 13px;
+ word-break: break-all;
+ word-wrap: break-word;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ border-radius: 4px
+}
+.container,
+.container-fluid {
+ margin-right: auto;
+ margin-left: auto
+}
+pre code {
+ padding: 0;
+ font-size: inherit;
+ color: inherit;
+ white-space: pre-wrap;
+ border-radius: 0
+}
+.container,
+.container-fluid {
+ padding-left: 15px;
+ padding-right: 15px
+}
+.pre-scrollable {
+ overflow-y: scroll
+}
+@media (min-width: 992px) {
+ .container {
+ width: 970px
+ }
+}
+@media (min-width: 1200px) {
+ .container {
+ width: 1170px
+ }
+}
+.row {
+ margin-left: -15px;
+ margin-right: -15px
+}
+.col-lg-1,
+.col-lg-10,
+.col-lg-11,
+.col-lg-12,
+.col-lg-2,
+.col-lg-3,
+.col-lg-4,
+.col-lg-5,
+.col-lg-6,
+.col-lg-7,
+.col-lg-8,
+.col-lg-9,
+.col-md-1,
+.col-md-10,
+.col-md-11,
+.col-md-12,
+.col-md-2,
+.col-md-3,
+.col-md-4,
+.col-md-5,
+.col-md-6,
+.col-md-7,
+.col-md-8,
+.col-md-9,
+.col-sm-1,
+.col-sm-10,
+.col-sm-11,
+.col-sm-12,
+.col-sm-2,
+.col-sm-3,
+.col-sm-4,
+.col-sm-5,
+.col-sm-6,
+.col-sm-7,
+.col-sm-8,
+.col-sm-9,
+.col-xs-1,
+.col-xs-10,
+.col-xs-11,
+.col-xs-12,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9 {
+ position: relative;
+ min-height: 1px;
+ padding-left: 15px;
+ padding-right: 15px
+}
+.col-xs-12 {
+ width: 100%
+}
+.col-xs-11 {
+ width: 91.66666667%
+}
+.col-xs-10 {
+ width: 83.33333333%
+}
+.col-xs-9 {
+ width: 75%
+}
+.col-xs-8 {
+ width: 66.66666667%
+}
+.col-xs-7 {
+ width: 58.33333333%
+}
+.col-xs-6 {
+ width: 50%
+}
+.col-xs-5 {
+ width: 41.66666667%
+}
+.col-xs-4 {
+ width: 33.33333333%
+}
+.col-xs-3 {
+ width: 25%
+}
+.col-xs-2 {
+ width: 16.66666667%
+}
+.col-xs-1 {
+ width: 8.33333333%
+}
+.col-xs-pull-12 {
+ right: 100%
+}
+.col-xs-pull-11 {
+ right: 91.66666667%
+}
+.col-xs-pull-10 {
+ right: 83.33333333%
+}
+.col-xs-pull-9 {
+ right: 75%
+}
+.col-xs-pull-8 {
+ right: 66.66666667%
+}
+.col-xs-pull-7 {
+ right: 58.33333333%
+}
+.col-xs-pull-6 {
+ right: 50%
+}
+.col-xs-pull-5 {
+ right: 41.66666667%
+}
+.col-xs-pull-4 {
+ right: 33.33333333%
+}
+.col-xs-pull-3 {
+ right: 25%
+}
+.col-xs-pull-2 {
+ right: 16.66666667%
+}
+.col-xs-pull-1 {
+ right: 8.33333333%
+}
+.col-xs-pull-0 {
+ right: auto
+}
+.col-xs-push-12 {
+ left: 100%
+}
+.col-xs-push-11 {
+ left: 91.66666667%
+}
+.col-xs-push-10 {
+ left: 83.33333333%
+}
+.col-xs-push-9 {
+ left: 75%
+}
+.col-xs-push-8 {
+ left: 66.66666667%
+}
+.col-xs-push-7 {
+ left: 58.33333333%
+}
+.col-xs-push-6 {
+ left: 50%
+}
+.col-xs-push-5 {
+ left: 41.66666667%
+}
+.col-xs-push-4 {
+ left: 33.33333333%
+}
+.col-xs-push-3 {
+ left: 25%
+}
+.col-xs-push-2 {
+ left: 16.66666667%
+}
+.col-xs-push-1 {
+ left: 8.33333333%
+}
+.col-xs-push-0 {
+ left: auto
+}
+.col-xs-offset-12 {
+ margin-left: 100%
+}
+.col-xs-offset-11 {
+ margin-left: 91.66666667%
+}
+.col-xs-offset-10 {
+ margin-left: 83.33333333%
+}
+.col-xs-offset-9 {
+ margin-left: 75%
+}
+.col-xs-offset-8 {
+ margin-left: 66.66666667%
+}
+.col-xs-offset-7 {
+ margin-left: 58.33333333%
+}
+.col-xs-offset-6 {
+ margin-left: 50%
+}
+.col-xs-offset-5 {
+ margin-left: 41.66666667%
+}
+.col-xs-offset-4 {
+ margin-left: 33.33333333%
+}
+.col-xs-offset-3 {
+ margin-left: 25%
+}
+.col-xs-offset-2 {
+ margin-left: 16.66666667%
+}
+.col-xs-offset-1 {
+ margin-left: 8.33333333%
+}
+.col-xs-offset-0 {
+ margin-left: 0
+}
+@media (min-width: 768px) {
+ .col-sm-1,
+ .col-sm-10,
+ .col-sm-11,
+ .col-sm-12,
+ .col-sm-2,
+ .col-sm-3,
+ .col-sm-4,
+ .col-sm-5,
+ .col-sm-6,
+ .col-sm-7,
+ .col-sm-8,
+ .col-sm-9 {
+ float: left
+ }
+ .col-sm-12 {
+ width: 100%
+ }
+ .col-sm-11 {
+ width: 91.66666667%
+ }
+ .col-sm-10 {
+ width: 83.33333333%
+ }
+ .col-sm-9 {
+ width: 75%
+ }
+ .col-sm-8 {
+ width: 66.66666667%
+ }
+ .col-sm-7 {
+ width: 58.33333333%
+ }
+ .col-sm-6 {
+ width: 50%
+ }
+ .col-sm-5 {
+ width: 41.66666667%
+ }
+ .col-sm-4 {
+ width: 33.33333333%
+ }
+ .col-sm-3 {
+ width: 25%
+ }
+ .col-sm-2 {
+ width: 16.66666667%
+ }
+ .col-sm-1 {
+ width: 8.33333333%
+ }
+ .col-sm-pull-12 {
+ right: 100%
+ }
+ .col-sm-pull-11 {
+ right: 91.66666667%
+ }
+ .col-sm-pull-10 {
+ right: 83.33333333%
+ }
+ .col-sm-pull-9 {
+ right: 75%
+ }
+ .col-sm-pull-8 {
+ right: 66.66666667%
+ }
+ .col-sm-pull-7 {
+ right: 58.33333333%
+ }
+ .col-sm-pull-6 {
+ right: 50%
+ }
+ .col-sm-pull-5 {
+ right: 41.66666667%
+ }
+ .col-sm-pull-4 {
+ right: 33.33333333%
+ }
+ .col-sm-pull-3 {
+ right: 25%
+ }
+ .col-sm-pull-2 {
+ right: 16.66666667%
+ }
+ .col-sm-pull-1 {
+ right: 8.33333333%
+ }
+ .col-sm-pull-0 {
+ right: auto
+ }
+ .col-sm-push-12 {
+ left: 100%
+ }
+ .col-sm-push-11 {
+ left: 91.66666667%
+ }
+ .col-sm-push-10 {
+ left: 83.33333333%
+ }
+ .col-sm-push-9 {
+ left: 75%
+ }
+ .col-sm-push-8 {
+ left: 66.66666667%
+ }
+ .col-sm-push-7 {
+ left: 58.33333333%
+ }
+ .col-sm-push-6 {
+ left: 50%
+ }
+ .col-sm-push-5 {
+ left: 41.66666667%
+ }
+ .col-sm-push-4 {
+ left: 33.33333333%
+ }
+ .col-sm-push-3 {
+ left: 25%
+ }
+ .col-sm-push-2 {
+ left: 16.66666667%
+ }
+ .col-sm-push-1 {
+ left: 8.33333333%
+ }
+ .col-sm-push-0 {
+ left: auto
+ }
+ .col-sm-offset-12 {
+ margin-left: 100%
+ }
+ .col-sm-offset-11 {
+ margin-left: 91.66666667%
+ }
+ .col-sm-offset-10 {
+ margin-left: 83.33333333%
+ }
+ .col-sm-offset-9 {
+ margin-left: 75%
+ }
+ .col-sm-offset-8 {
+ margin-left: 66.66666667%
+ }
+ .col-sm-offset-7 {
+ margin-left: 58.33333333%
+ }
+ .col-sm-offset-6 {
+ margin-left: 50%
+ }
+ .col-sm-offset-5 {
+ margin-left: 41.66666667%
+ }
+ .col-sm-offset-4 {
+ margin-left: 33.33333333%
+ }
+ .col-sm-offset-3 {
+ margin-left: 25%
+ }
+ .col-sm-offset-2 {
+ margin-left: 16.66666667%
+ }
+ .col-sm-offset-1 {
+ margin-left: 8.33333333%
+ }
+ .col-sm-offset-0 {
+ margin-left: 0
+ }
+}
+@media (min-width: 992px) {
+ .col-md-1,
+ .col-md-10,
+ .col-md-11,
+ .col-md-12,
+ .col-md-2,
+ .col-md-3,
+ .col-md-4,
+ .col-md-5,
+ .col-md-6,
+ .col-md-7,
+ .col-md-8,
+ .col-md-9 {
+ float: left
+ }
+ .col-md-12 {
+ width: 100%
+ }
+ .col-md-11 {
+ width: 91.66666667%
+ }
+ .col-md-10 {
+ width: 83.33333333%
+ }
+ .col-md-9 {
+ width: 75%
+ }
+ .col-md-8 {
+ width: 66.66666667%
+ }
+ .col-md-7 {
+ width: 58.33333333%
+ }
+ .col-md-6 {
+ width: 50%
+ }
+ .col-md-5 {
+ width: 41.66666667%
+ }
+ .col-md-4 {
+ width: 33.33333333%
+ }
+ .col-md-3 {
+ width: 25%
+ }
+ .col-md-2 {
+ width: 16.66666667%
+ }
+ .col-md-1 {
+ width: 8.33333333%
+ }
+ .col-md-pull-12 {
+ right: 100%
+ }
+ .col-md-pull-11 {
+ right: 91.66666667%
+ }
+ .col-md-pull-10 {
+ right: 83.33333333%
+ }
+ .col-md-pull-9 {
+ right: 75%
+ }
+ .col-md-pull-8 {
+ right: 66.66666667%
+ }
+ .col-md-pull-7 {
+ right: 58.33333333%
+ }
+ .col-md-pull-6 {
+ right: 50%
+ }
+ .col-md-pull-5 {
+ right: 41.66666667%
+ }
+ .col-md-pull-4 {
+ right: 33.33333333%
+ }
+ .col-md-pull-3 {
+ right: 25%
+ }
+ .col-md-pull-2 {
+ right: 16.66666667%
+ }
+ .col-md-pull-1 {
+ right: 8.33333333%
+ }
+ .col-md-pull-0 {
+ right: auto
+ }
+ .col-md-push-12 {
+ left: 100%
+ }
+ .col-md-push-11 {
+ left: 91.66666667%
+ }
+ .col-md-push-10 {
+ left: 83.33333333%
+ }
+ .col-md-push-9 {
+ left: 75%
+ }
+ .col-md-push-8 {
+ left: 66.66666667%
+ }
+ .col-md-push-7 {
+ left: 58.33333333%
+ }
+ .col-md-push-6 {
+ left: 50%
+ }
+ .col-md-push-5 {
+ left: 41.66666667%
+ }
+ .col-md-push-4 {
+ left: 33.33333333%
+ }
+ .col-md-push-3 {
+ left: 25%
+ }
+ .col-md-push-2 {
+ left: 16.66666667%
+ }
+ .col-md-push-1 {
+ left: 8.33333333%
+ }
+ .col-md-push-0 {
+ left: auto
+ }
+ .col-md-offset-12 {
+ margin-left: 100%
+ }
+ .col-md-offset-11 {
+ margin-left: 91.66666667%
+ }
+ .col-md-offset-10 {
+ margin-left: 83.33333333%
+ }
+ .col-md-offset-9 {
+ margin-left: 75%
+ }
+ .col-md-offset-8 {
+ margin-left: 66.66666667%
+ }
+ .col-md-offset-7 {
+ margin-left: 58.33333333%
+ }
+ .col-md-offset-6 {
+ margin-left: 50%
+ }
+ .col-md-offset-5 {
+ margin-left: 41.66666667%
+ }
+ .col-md-offset-4 {
+ margin-left: 33.33333333%
+ }
+ .col-md-offset-3 {
+ margin-left: 25%
+ }
+ .col-md-offset-2 {
+ margin-left: 16.66666667%
+ }
+ .col-md-offset-1 {
+ margin-left: 8.33333333%
+ }
+ .col-md-offset-0 {
+ margin-left: 0
+ }
+}
+@media (min-width: 1200px) {
+ .col-lg-1,
+ .col-lg-10,
+ .col-lg-11,
+ .col-lg-12,
+ .col-lg-2,
+ .col-lg-3,
+ .col-lg-4,
+ .col-lg-5,
+ .col-lg-6,
+ .col-lg-7,
+ .col-lg-8,
+ .col-lg-9 {
+ float: left
+ }
+ .col-lg-12 {
+ width: 100%
+ }
+ .col-lg-11 {
+ width: 91.66666667%
+ }
+ .col-lg-10 {
+ width: 83.33333333%
+ }
+ .col-lg-9 {
+ width: 75%
+ }
+ .col-lg-8 {
+ width: 66.66666667%
+ }
+ .col-lg-7 {
+ width: 58.33333333%
+ }
+ .col-lg-6 {
+ width: 50%
+ }
+ .col-lg-5 {
+ width: 41.66666667%
+ }
+ .col-lg-4 {
+ width: 33.33333333%
+ }
+ .col-lg-3 {
+ width: 25%
+ }
+ .col-lg-2 {
+ width: 16.66666667%
+ }
+ .col-lg-1 {
+ width: 8.33333333%
+ }
+ .col-lg-pull-12 {
+ right: 100%
+ }
+ .col-lg-pull-11 {
+ right: 91.66666667%
+ }
+ .col-lg-pull-10 {
+ right: 83.33333333%
+ }
+ .col-lg-pull-9 {
+ right: 75%
+ }
+ .col-lg-pull-8 {
+ right: 66.66666667%
+ }
+ .col-lg-pull-7 {
+ right: 58.33333333%
+ }
+ .col-lg-pull-6 {
+ right: 50%
+ }
+ .col-lg-pull-5 {
+ right: 41.66666667%
+ }
+ .col-lg-pull-4 {
+ right: 33.33333333%
+ }
+ .col-lg-pull-3 {
+ right: 25%
+ }
+ .col-lg-pull-2 {
+ right: 16.66666667%
+ }
+ .col-lg-pull-1 {
+ right: 8.33333333%
+ }
+ .col-lg-pull-0 {
+ right: auto
+ }
+ .col-lg-push-12 {
+ left: 100%
+ }
+ .col-lg-push-11 {
+ left: 91.66666667%
+ }
+ .col-lg-push-10 {
+ left: 83.33333333%
+ }
+ .col-lg-push-9 {
+ left: 75%
+ }
+ .col-lg-push-8 {
+ left: 66.66666667%
+ }
+ .col-lg-push-7 {
+ left: 58.33333333%
+ }
+ .col-lg-push-6 {
+ left: 50%
+ }
+ .col-lg-push-5 {
+ left: 41.66666667%
+ }
+ .col-lg-push-4 {
+ left: 33.33333333%
+ }
+ .col-lg-push-3 {
+ left: 25%
+ }
+ .col-lg-push-2 {
+ left: 16.66666667%
+ }
+ .col-lg-push-1 {
+ left: 8.33333333%
+ }
+ .col-lg-push-0 {
+ left: auto
+ }
+ .col-lg-offset-12 {
+ margin-left: 100%
+ }
+ .col-lg-offset-11 {
+ margin-left: 91.66666667%
+ }
+ .col-lg-offset-10 {
+ margin-left: 83.33333333%
+ }
+ .col-lg-offset-9 {
+ margin-left: 75%
+ }
+ .col-lg-offset-8 {
+ margin-left: 66.66666667%
+ }
+ .col-lg-offset-7 {
+ margin-left: 58.33333333%
+ }
+ .col-lg-offset-6 {
+ margin-left: 50%
+ }
+ .col-lg-offset-5 {
+ margin-left: 41.66666667%
+ }
+ .col-lg-offset-4 {
+ margin-left: 33.33333333%
+ }
+ .col-lg-offset-3 {
+ margin-left: 25%
+ }
+ .col-lg-offset-2 {
+ margin-left: 16.66666667%
+ }
+ .col-lg-offset-1 {
+ margin-left: 8.33333333%
+ }
+ .col-lg-offset-0 {
+ margin-left: 0
+ }
+}
+th {
+ text-align: left
+}
+.table {
+ width: 100%;
+ max-width: 100%;
+ margin-bottom: 20px
+}
+.table>tbody>tr>td,
+.table>tbody>tr>th,
+.table>tfoot>tr>td,
+.table>tfoot>tr>th,
+.table>thead>tr>td,
+.table>thead>tr>th {
+ padding: 8px;
+ line-height: 1.42857143;
+ vertical-align: top;
+ border-top: 1px solid #ddd
+}
+.table>thead>tr>th {
+ vertical-align: bottom;
+ border-bottom: 2px solid #ddd
+}
+.table>caption+thead>tr:first-child>td,
+.table>caption+thead>tr:first-child>th,
+.table>colgroup+thead>tr:first-child>td,
+.table>colgroup+thead>tr:first-child>th,
+.table>thead:first-child>tr:first-child>td,
+.table>thead:first-child>tr:first-child>th {
+ border-top: 0
+}
+.table>tbody+tbody {
+ border-top: 2px solid #ddd
+}
+.table .table {
+ background-color: #fff
+}
+.table-condensed>tbody>tr>td,
+.table-condensed>tbody>tr>th,
+.table-condensed>tfoot>tr>td,
+.table-condensed>tfoot>tr>th,
+.table-condensed>thead>tr>td,
+.table-condensed>thead>tr>th {
+ padding: 5px
+}
+.table-bordered,
+.table-bordered>tbody>tr>td,
+.table-bordered>tbody>tr>th,
+.table-bordered>tfoot>tr>td,
+.table-bordered>tfoot>tr>th,
+.table-bordered>thead>tr>td,
+.table-bordered>thead>tr>th {
+ border: 1px solid #ddd
+}
+.table-bordered>thead>tr>td,
+.table-bordered>thead>tr>th {
+ border-bottom-width: 2px
+}
+.table-striped>tbody>tr:nth-child(odd)>td,
+.table-striped>tbody>tr:nth-child(odd)>th {
+ background-color: #f9f9f9
+}
+.table-hover>tbody>tr:hover>td,
+.table-hover>tbody>tr:hover>th,
+.table>tbody>tr.active>td,
+.table>tbody>tr.active>th,
+.table>tbody>tr>td.active,
+.table>tbody>tr>th.active,
+.table>tfoot>tr.active>td,
+.table>tfoot>tr.active>th,
+.table>tfoot>tr>td.active,
+.table>tfoot>tr>th.active,
+.table>thead>tr.active>td,
+.table>thead>tr.active>th,
+.table>thead>tr>td.active,
+.table>thead>tr>th.active {
+ background-color: #f5f5f5
+}
+table col[class*=col-] {
+ position: static;
+ float: none;
+ display: table-column
+}
+table td[class*=col-],
+table th[class*=col-] {
+ position: static;
+ float: none;
+ display: table-cell
+}
+.table-hover>tbody>tr.active:hover>td,
+.table-hover>tbody>tr.active:hover>th,
+.table-hover>tbody>tr:hover>.active,
+.table-hover>tbody>tr>td.active:hover,
+.table-hover>tbody>tr>th.active:hover {
+ background-color: #e8e8e8
+}
+.table>tbody>tr.success>td,
+.table>tbody>tr.success>th,
+.table>tbody>tr>td.success,
+.table>tbody>tr>th.success,
+.table>tfoot>tr.success>td,
+.table>tfoot>tr.success>th,
+.table>tfoot>tr>td.success,
+.table>tfoot>tr>th.success,
+.table>thead>tr.success>td,
+.table>thead>tr.success>th,
+.table>thead>tr>td.success,
+.table>thead>tr>th.success {
+ background-color: #dff0d8
+}
+.table-hover>tbody>tr.success:hover>td,
+.table-hover>tbody>tr.success:hover>th,
+.table-hover>tbody>tr:hover>.success,
+.table-hover>tbody>tr>td.success:hover,
+.table-hover>tbody>tr>th.success:hover {
+ background-color: #d0e9c6
+}
+.table>tbody>tr.info>td,
+.table>tbody>tr.info>th,
+.table>tbody>tr>td.info,
+.table>tbody>tr>th.info,
+.table>tfoot>tr.info>td,
+.table>tfoot>tr.info>th,
+.table>tfoot>tr>td.info,
+.table>tfoot>tr>th.info,
+.table>thead>tr.info>td,
+.table>thead>tr.info>th,
+.table>thead>tr>td.info,
+.table>thead>tr>th.info {
+ background-color: #d9edf7
+}
+.table-hover>tbody>tr.info:hover>td,
+.table-hover>tbody>tr.info:hover>th,
+.table-hover>tbody>tr:hover>.info,
+.table-hover>tbody>tr>td.info:hover,
+.table-hover>tbody>tr>th.info:hover {
+ background-color: #c4e3f3
+}
+.table>tbody>tr.warning>td,
+.table>tbody>tr.warning>th,
+.table>tbody>tr>td.warning,
+.table>tbody>tr>th.warning,
+.table>tfoot>tr.warning>td,
+.table>tfoot>tr.warning>th,
+.table>tfoot>tr>td.warning,
+.table>tfoot>tr>th.warning,
+.table>thead>tr.warning>td,
+.table>thead>tr.warning>th,
+.table>thead>tr>td.warning,
+.table>thead>tr>th.warning {
+ background-color: #fcf8e3
+}
+.table-hover>tbody>tr.warning:hover>td,
+.table-hover>tbody>tr.warning:hover>th,
+.table-hover>tbody>tr:hover>.warning,
+.table-hover>tbody>tr>td.warning:hover,
+.table-hover>tbody>tr>th.warning:hover {
+ background-color: #faf2cc
+}
+.table>tbody>tr.danger>td,
+.table>tbody>tr.danger>th,
+.table>tbody>tr>td.danger,
+.table>tbody>tr>th.danger,
+.table>tfoot>tr.danger>td,
+.table>tfoot>tr.danger>th,
+.table>tfoot>tr>td.danger,
+.table>tfoot>tr>th.danger,
+.table>thead>tr.danger>td,
+.table>thead>tr.danger>th,
+.table>thead>tr>td.danger,
+.table>thead>tr>th.danger {
+ background-color: #f2dede
+}
+.table-hover>tbody>tr.danger:hover>td,
+.table-hover>tbody>tr.danger:hover>th,
+.table-hover>tbody>tr:hover>.danger,
+.table-hover>tbody>tr>td.danger:hover,
+.table-hover>tbody>tr>th.danger:hover {
+ background-color: #ebcccc
+}
+@media screen and (max-width: 767px) {
+ .table-responsive {
+ width: 100%;
+ margin-bottom: 15px;
+ overflow-y: hidden;
+ overflow-x: auto;
+ -ms-overflow-style: -ms-autohiding-scrollbar;
+ border: 1px solid #ddd;
+ -webkit-overflow-scrolling: touch
+ }
+ .table-responsive>.table {
+ margin-bottom: 0
+ }
+ .table-responsive>.table>tbody>tr>td,
+ .table-responsive>.table>tbody>tr>th,
+ .table-responsive>.table>tfoot>tr>td,
+ .table-responsive>.table>tfoot>tr>th,
+ .table-responsive>.table>thead>tr>td,
+ .table-responsive>.table>thead>tr>th {
+ white-space: nowrap
+ }
+ .table-responsive>.table-bordered {
+ border: 0
+ }
+ .table-responsive>.table-bordered>tbody>tr>td:first-child,
+ .table-responsive>.table-bordered>tbody>tr>th:first-child,
+ .table-responsive>.table-bordered>tfoot>tr>td:first-child,
+ .table-responsive>.table-bordered>tfoot>tr>th:first-child,
+ .table-responsive>.table-bordered>thead>tr>td:first-child,
+ .table-responsive>.table-bordered>thead>tr>th:first-child {
+ border-left: 0
+ }
+ .table-responsive>.table-bordered>tbody>tr>td:last-child,
+ .table-responsive>.table-bordered>tbody>tr>th:last-child,
+ .table-responsive>.table-bordered>tfoot>tr>td:last-child,
+ .table-responsive>.table-bordered>tfoot>tr>th:last-child,
+ .table-responsive>.table-bordered>thead>tr>td:last-child,
+ .table-responsive>.table-bordered>thead>tr>th:last-child {
+ border-right: 0
+ }
+ .table-responsive>.table-bordered>tbody>tr:last-child>td,
+ .table-responsive>.table-bordered>tbody>tr:last-child>th,
+ .table-responsive>.table-bordered>tfoot>tr:last-child>td,
+ .table-responsive>.table-bordered>tfoot>tr:last-child>th {
+ border-bottom: 0
+ }
+}
+fieldset,
+legend {
+ padding: 0;
+ border: 0
+}
+fieldset {
+ margin: 0;
+ min-width: 0
+}
+legend {
+ width: 100%;
+ margin-bottom: 20px;
+ font-size: 21px;
+ line-height: inherit;
+ border-bottom: 1px solid #e5e5e5
+}
+label {
+ display: inline-block;
+ max-width: 100%;
+ margin-bottom: 5px
+}
+input[type=search] {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-appearance: none
+}
+input[type=checkbox],
+input[type=radio] {
+ margin: 4px 0 0;
+ margin-top: 1px\9;
+ line-height: normal
+}
+.form-control,
+output {
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+ display: block
+}
+input[type=file] {
+ display: block
+}
+input[type=range] {
+ display: block;
+ width: 100%
+}
+select[multiple],
+select[size] {
+ height: auto
+}
+input[type=file]:focus,
+input[type=checkbox]:focus,
+input[type=radio]:focus {
+ outline: dotted thin;
+ outline: -webkit-focus-ring-color auto 5px;
+ outline-offset: -2px
+}
+output {
+ padding-top: 7px
+}
+.form-control {
+ width: 100%;
+ height: 34px;
+ padding: 6px 9pt;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+ -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+ transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s
+}
+.form-control:focus {
+ border-color: #66afe9;
+ outline: 0;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6)
+}
+.form-control::-moz-placeholder {
+ color: #777;
+ opacity: 1
+}
+.form-control:-ms-input-placeholder {
+ color: #777
+}
+.form-control::-webkit-input-placeholder {
+ color: #777
+}
+.has-success .checkbox,
+.has-success .checkbox-inline,
+.has-success .control-label,
+.has-success .form-control-feedback,
+.has-success .help-block,
+.has-success .radio,
+.has-success .radio-inline {
+ color: #3c763d
+}
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+ cursor: not-allowed;
+ background-color: #eee;
+ opacity: 1
+}
+textarea.form-control {
+ height: auto
+}
+input[type=date],
+input[type=time],
+input[type=datetime-local],
+input[type=month] {
+ line-height: 34px;
+ line-height: 1.42857143\9
+}
+input[type=date].input-sm,
+input[type=time].input-sm,
+input[type=datetime-local].input-sm,
+input[type=month].input-sm {
+ line-height: 30px
+}
+input[type=date].input-lg,
+input[type=time].input-lg,
+input[type=datetime-local].input-lg,
+input[type=month].input-lg {
+ line-height: 46px
+}
+.form-group {
+ margin-bottom: 15px
+}
+.checkbox,
+.radio {
+ position: relative;
+ display: block;
+ min-height: 20px;
+ margin-top: 10px;
+ margin-bottom: 10px
+}
+.checkbox label,
+.radio label {
+ padding-left: 20px;
+ margin-bottom: 0;
+ font-weight: 400;
+ cursor: pointer
+}
+.checkbox input[type=checkbox],
+.checkbox-inline input[type=checkbox],
+.radio input[type=radio],
+.radio-inline input[type=radio] {
+ position: absolute;
+ margin-left: -20px;
+ margin-top: 4px\9
+}
+.checkbox+.checkbox,
+.radio+.radio {
+ margin-top: -5px
+}
+.checkbox-inline,
+.radio-inline {
+ display: inline-block;
+ padding-left: 20px;
+ margin-bottom: 0;
+ vertical-align: middle;
+ font-weight: 400;
+ cursor: pointer
+}
+.checkbox-inline+.checkbox-inline,
+.radio-inline+.radio-inline {
+ margin-top: 0;
+ margin-left: 10px
+}
+.checkbox-inline.disabled,
+.checkbox.disabled label,
+.radio-inline.disabled,
+.radio.disabled label,
+fieldset[disabled] .checkbox label,
+fieldset[disabled] .checkbox-inline,
+fieldset[disabled] .radio label,
+fieldset[disabled] .radio-inline,
+fieldset[disabled] input[type=checkbox],
+fieldset[disabled] input[type=radio],
+input[type=checkbox].disabled,
+input[type=checkbox][disabled],
+input[type=radio].disabled,
+input[type=radio][disabled] {
+ cursor: not-allowed
+}
+.form-control-static {
+ padding-top: 7px;
+ padding-bottom: 7px;
+ margin-bottom: 0
+}
+.form-control-static.input-lg,
+.form-control-static.input-sm {
+ padding-left: 0;
+ padding-right: 0
+}
+.form-horizontal .form-group-sm .form-control,
+.input-sm {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 9pt;
+ line-height: 1.5;
+ border-radius: 3px
+}
+select.input-sm {
+ height: 30px;
+ line-height: 30px
+}
+select[multiple].input-sm,
+textarea.input-sm {
+ height: auto
+}
+.form-horizontal .form-group-lg .form-control,
+.input-lg {
+ height: 46px;
+ padding: 10px 1pc;
+ font-size: 18px;
+ line-height: 1.33;
+ border-radius: 6px
+}
+select.input-lg {
+ height: 46px;
+ line-height: 46px
+}
+select[multiple].input-lg,
+textarea.input-lg {
+ height: auto
+}
+.has-feedback {
+ position: relative
+}
+.has-feedback .form-control {
+ padding-right: 42.5px
+}
+.form-control-feedback {
+ position: absolute;
+ top: 25px;
+ right: 0;
+ z-index: 2;
+ display: block;
+ width: 34px;
+ height: 34px;
+ line-height: 34px;
+ text-align: center
+}
+.collapsing,
+.dropdown {
+ position: relative
+}
+.input-lg+.form-control-feedback {
+ width: 46px;
+ height: 46px;
+ line-height: 46px
+}
+.input-sm+.form-control-feedback {
+ width: 30px;
+ height: 30px;
+ line-height: 30px
+}
+.has-success .form-control {
+ border-color: #3c763d;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075)
+}
+.has-success .form-control:focus {
+ border-color: #2b542c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168
+}
+.has-success .input-group-addon {
+ color: #3c763d;
+ border-color: #3c763d;
+ background-color: #dff0d8
+}
+.has-warning .checkbox,
+.has-warning .checkbox-inline,
+.has-warning .control-label,
+.has-warning .form-control-feedback,
+.has-warning .help-block,
+.has-warning .radio,
+.has-warning .radio-inline {
+ color: #8a6d3b
+}
+.has-warning .form-control {
+ border-color: #8a6d3b;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075)
+}
+.has-warning .form-control:focus {
+ border-color: #66512c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b
+}
+.has-warning .input-group-addon {
+ color: #8a6d3b;
+ border-color: #8a6d3b;
+ background-color: #fcf8e3
+}
+.has-error .checkbox,
+.has-error .checkbox-inline,
+.has-error .control-label,
+.has-error .form-control-feedback,
+.has-error .help-block,
+.has-error .radio,
+.has-error .radio-inline {
+ color: #a94442
+}
+.has-error .form-control {
+ border-color: #a94442;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075)
+}
+.has-error .form-control:focus {
+ border-color: #843534;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483
+}
+.has-error .input-group-addon {
+ color: #a94442;
+ border-color: #a94442;
+ background-color: #f2dede
+}
+.has-feedback label.sr-only~.form-control-feedback {
+ top: 0
+}
+.help-block {
+ display: block;
+ margin-top: 5px;
+ margin-bottom: 10px;
+ color: #737373
+}
+@media (min-width: 768px) {
+ .form-inline .control-label,
+ .form-inline .form-group {
+ margin-bottom: 0;
+ vertical-align: middle
+ }
+ .form-inline .form-group {
+ display: inline-block
+ }
+ .form-inline .form-control {
+ display: inline-block;
+ width: auto;
+ vertical-align: middle
+ }
+ .form-inline .input-group {
+ display: inline-table;
+ vertical-align: middle
+ }
+ .form-inline .input-group .form-control,
+ .form-inline .input-group .input-group-addon,
+ .form-inline .input-group .input-group-btn {
+ width: auto
+ }
+ .form-inline .input-group>.form-control {
+ width: 100%
+ }
+ .form-inline .checkbox,
+ .form-inline .radio {
+ display: inline-block;
+ margin-top: 0;
+ margin-bottom: 0;
+ vertical-align: middle
+ }
+ .form-inline .checkbox label,
+ .form-inline .radio label {
+ padding-left: 0
+ }
+ .form-inline .checkbox input[type=checkbox],
+ .form-inline .radio input[type=radio] {
+ position: relative;
+ margin-left: 0
+ }
+ .form-inline .has-feedback .form-control-feedback {
+ top: 0
+ }
+ .form-horizontal .control-label {
+ text-align: right;
+ margin-bottom: 0;
+ padding-top: 7px
+ }
+}
+.form-horizontal .checkbox,
+.form-horizontal .checkbox-inline,
+.form-horizontal .radio,
+.form-horizontal .radio-inline {
+ margin-top: 0;
+ margin-bottom: 0;
+ padding-top: 7px
+}
+.form-horizontal .checkbox,
+.form-horizontal .radio {
+ min-height: 27px
+}
+.form-horizontal .form-group {
+ margin-left: -15px;
+ margin-right: -15px
+}
+.form-horizontal .has-feedback .form-control-feedback {
+ top: 0;
+ right: 15px
+}
+@media (min-width: 768px) {
+ .form-horizontal .form-group-lg .control-label {
+ padding-top: 14.3px
+ }
+ .form-horizontal .form-group-sm .control-label {
+ padding-top: 6px
+ }
+}
+.btn {
+ display: inline-block;
+ margin-bottom: 0;
+ font-weight: 400;
+ text-align: center;
+ vertical-align: middle;
+ cursor: pointer;
+ border: 1px solid transparent;
+ white-space: nowrap;
+ padding: 6px 9pt;
+ font-size: 14px;
+ line-height: 1.42857143;
+ border-radius: 4px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none
+}
+.btn.active:focus,
+.btn:active:focus,
+.btn:focus {
+ outline: dotted thin;
+ outline: -webkit-focus-ring-color auto 5px;
+ outline-offset: -2px
+}
+.btn:focus,
+.btn:hover {
+ color: #333;
+ text-decoration: none
+}
+.btn.active,
+.btn:active {
+ outline: 0;
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125)
+}
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+ cursor: not-allowed;
+ pointer-events: none;
+ opacity: .65;
+ filter: alpha(opacity=65);
+ -webkit-box-shadow: none;
+ box-shadow: none
+}
+.btn-default {
+ color: #333;
+ background-color: #fff;
+ border-color: #ccc
+}
+.btn-default.active,
+.btn-default:active,
+.btn-default:focus,
+.btn-default:hover,
+.open>.dropdown-toggle.btn-default {
+ color: #333;
+ background-color: #e6e6e6;
+ border-color: #adadad
+}
+.btn-default.disabled,
+.btn-default.disabled.active,
+.btn-default.disabled:active,
+.btn-default.disabled:focus,
+.btn-default.disabled:hover,
+.btn-default[disabled],
+.btn-default[disabled].active,
+.btn-default[disabled]:active,
+.btn-default[disabled]:focus,
+.btn-default[disabled]:hover,
+fieldset[disabled] .btn-default,
+fieldset[disabled] .btn-default.active,
+fieldset[disabled] .btn-default:active,
+fieldset[disabled] .btn-default:focus,
+fieldset[disabled] .btn-default:hover {
+ background-color: #fff;
+ border-color: #ccc
+}
+.btn-default .badge {
+ color: #fff;
+ background-color: #333
+}
+.btn-primary {
+ color: #fff;
+ background-color: #428bca;
+ border-color: #357ebd
+}
+.btn-primary.active,
+.btn-primary:active,
+.btn-primary:focus,
+.btn-primary:hover,
+.open>.dropdown-toggle.btn-primary {
+ color: #fff;
+ background-color: #3071a9;
+ border-color: #285e8e
+}
+.btn-primary.disabled,
+.btn-primary.disabled.active,
+.btn-primary.disabled:active,
+.btn-primary.disabled:focus,
+.btn-primary.disabled:hover,
+.btn-primary[disabled],
+.btn-primary[disabled].active,
+.btn-primary[disabled]:active,
+.btn-primary[disabled]:focus,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary,
+fieldset[disabled] .btn-primary.active,
+fieldset[disabled] .btn-primary:active,
+fieldset[disabled] .btn-primary:focus,
+fieldset[disabled] .btn-primary:hover {
+ background-color: #428bca;
+ border-color: #357ebd
+}
+.btn-primary .badge {
+ color: #428bca;
+ background-color: #fff
+}
+.btn-success {
+ color: #fff;
+ background-color: #5cb85c;
+ border-color: #4cae4c
+}
+.btn-success.active,
+.btn-success:active,
+.btn-success:focus,
+.btn-success:hover,
+.open>.dropdown-toggle.btn-success {
+ color: #fff;
+ background-color: #449d44;
+ border-color: #398439
+}
+.btn-success.active,
+.btn-success:active,
+.open>.dropdown-toggle.btn-success {
+ background-image: none
+}
+.btn-success.disabled,
+.btn-success.disabled.active,
+.btn-success.disabled:active,
+.btn-success.disabled:focus,
+.btn-success.disabled:hover,
+.btn-success[disabled],
+.btn-success[disabled].active,
+.btn-success[disabled]:active,
+.btn-success[disabled]:focus,
+.btn-success[disabled]:hover,
+fieldset[disabled] .btn-success,
+fieldset[disabled] .btn-success.active,
+fieldset[disabled] .btn-success:active,
+fieldset[disabled] .btn-success:focus,
+fieldset[disabled] .btn-success:hover {
+ background-color: #5cb85c;
+ border-color: #4cae4c
+}
+.btn-success .badge {
+ color: #5cb85c;
+ background-color: #fff
+}
+.btn-info {
+ color: #fff;
+ background-color: #5bc0de;
+ border-color: #46b8da
+}
+.btn-info.active,
+.btn-info:active,
+.btn-info:focus,
+.btn-info:hover,
+.open>.dropdown-toggle.btn-info {
+ color: #fff;
+ background-color: #31b0d5;
+ border-color: #269abc
+}
+.btn-info.disabled,
+.btn-info.disabled.active,
+.btn-info.disabled:active,
+.btn-info.disabled:focus,
+.btn-info.disabled:hover,
+.btn-info[disabled],
+.btn-info[disabled].active,
+.btn-info[disabled]:active,
+.btn-info[disabled]:focus,
+.btn-info[disabled]:hover,
+fieldset[disabled] .btn-info,
+fieldset[disabled] .btn-info.active,
+fieldset[disabled] .btn-info:active,
+fieldset[disabled] .btn-info:focus,
+fieldset[disabled] .btn-info:hover {
+ background-color: #5bc0de;
+ border-color: #46b8da
+}
+.btn-info .badge {
+ color: #5bc0de;
+ background-color: #fff
+}
+.btn-warning {
+ color: #fff;
+ background-color: #f0ad4e;
+ border-color: #eea236
+}
+.btn-warning.active,
+.btn-warning:active,
+.btn-warning:focus,
+.btn-warning:hover,
+.open>.dropdown-toggle.btn-warning {
+ color: #fff;
+ background-color: #ec971f;
+ border-color: #d58512
+}
+.btn-warning.disabled,
+.btn-warning.disabled.active,
+.btn-warning.disabled:active,
+.btn-warning.disabled:focus,
+.btn-warning.disabled:hover,
+.btn-warning[disabled],
+.btn-warning[disabled].active,
+.btn-warning[disabled]:active,
+.btn-warning[disabled]:focus,
+.btn-warning[disabled]:hover,
+fieldset[disabled] .btn-warning,
+fieldset[disabled] .btn-warning.active,
+fieldset[disabled] .btn-warning:active,
+fieldset[disabled] .btn-warning:focus,
+fieldset[disabled] .btn-warning:hover {
+ background-color: #f0ad4e;
+ border-color: #eea236
+}
+.btn-warning .badge {
+ color: #f0ad4e;
+ background-color: #fff
+}
+.btn-danger {
+ color: #fff;
+ background-color: #d9534f;
+ border-color: #d43f3a
+}
+.btn-danger.active,
+.btn-danger:active,
+.btn-danger:focus,
+.btn-danger:hover,
+.open>.dropdown-toggle.btn-danger {
+ color: #fff;
+ background-color: #c9302c;
+ border-color: #ac2925
+}
+.btn-danger.disabled,
+.btn-danger.disabled.active,
+.btn-danger.disabled:active,
+.btn-danger.disabled:focus,
+.btn-danger.disabled:hover,
+.btn-danger[disabled],
+.btn-danger[disabled].active,
+.btn-danger[disabled]:active,
+.btn-danger[disabled]:focus,
+.btn-danger[disabled]:hover,
+fieldset[disabled] .btn-danger,
+fieldset[disabled] .btn-danger.active,
+fieldset[disabled] .btn-danger:active,
+fieldset[disabled] .btn-danger:focus,
+fieldset[disabled] .btn-danger:hover {
+ background-color: #d9534f;
+ border-color: #d43f3a
+}
+.btn-danger .badge {
+ color: #d9534f;
+ background-color: #fff
+}
+.btn-link {
+ color: #428bca;
+ font-weight: 400;
+ cursor: pointer;
+ border-radius: 0
+}
+.btn-link,
+.btn-link:active,
+.btn-link[disabled],
+fieldset[disabled] .btn-link {
+ background-color: transparent;
+ -webkit-box-shadow: none;
+ box-shadow: none
+}
+.btn-link,
+.btn-link:active,
+.btn-link:focus,
+.btn-link:hover {
+ border-color: transparent
+}
+.btn-link:focus,
+.btn-link:hover {
+ color: #2a6496;
+ text-decoration: underline;
+ background-color: transparent
+}
+.btn-link[disabled]:focus,
+.btn-link[disabled]:hover,
+fieldset[disabled] .btn-link:focus,
+fieldset[disabled] .btn-link:hover {
+ color: #777;
+ text-decoration: none
+}
+.btn-group-lg>.btn,
+.btn-lg {
+ padding: 10px 1pc;
+ font-size: 18px;
+ line-height: 1.33;
+ border-radius: 6px
+}
+.btn-group-sm>.btn,
+.btn-sm {
+ padding: 5px 10px;
+ font-size: 9pt;
+ line-height: 1.5;
+ border-radius: 3px
+}
+.btn-group-xs>.btn,
+.btn-xs {
+ padding: 1px 5px;
+ font-size: 9pt;
+ line-height: 1.5;
+ border-radius: 3px
+}
+.btn-block {
+ display: block;
+ width: 100%
+}
+.btn-block+.btn-block {
+ margin-top: 5px
+}
+input[type=button].btn-block,
+input[type=reset].btn-block,
+input[type=submit].btn-block {
+ width: 100%
+}
+.fade {
+ opacity: 0;
+ -webkit-transition: opacity .15s linear;
+ -o-transition: opacity .15s linear;
+ transition: opacity .15s linear
+}
+.fade.in {
+ opacity: 1
+}
+.collapse {
+ display: none
+}
+.collapse.in {
+ display: block
+}
+tr.collapse.in {
+ display: table-row
+}
+tbody.collapse.in {
+ display: table-row-group
+}
+.collapsing {
+ height: 0;
+ overflow: hidden;
+ -webkit-transition: height .35s ease;
+ -o-transition: height .35s ease;
+ transition: height .35s ease
+}
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ margin-left: 2px;
+ vertical-align: middle;
+ border-top: 4px solid;
+ border-right: 4px solid transparent;
+ border-left: 4px solid transparent
+}
+.dropdown-toggle:focus {
+ outline: 0
+}
+.dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ min-width: 10pc;
+ padding: 5px 0;
+ margin: 2px 0 0;
+ list-style: none;
+ font-size: 14px;
+ text-align: left;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, .15);
+ border-radius: 4px;
+ -webkit-box-shadow: 0 6px 9pt rgba(0, 0, 0, .175);
+ box-shadow: 0 6px 9pt rgba(0, 0, 0, .175);
+ background-clip: padding-box
+}
+.dropdown-menu-right,
+.dropdown-menu.pull-right {
+ left: auto;
+ right: 0
+}
+.dropdown-header,
+.dropdown-menu>li>a {
+ display: block;
+ padding: 3px 20px;
+ line-height: 1.42857143;
+ white-space: nowrap
+}
+.btn-group-vertical>.btn:not(:first-child):not(:last-child),
+.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn,
+.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+ border-radius: 0
+}
+.dropdown-menu .divider {
+ height: 1px;
+ margin: 9px 0;
+ overflow: hidden;
+ background-color: #e5e5e5
+}
+.dropdown-menu>li>a {
+ clear: both;
+ font-weight: 400;
+ color: #333
+}
+.dropdown-menu>li>a:focus,
+.dropdown-menu>li>a:hover {
+ text-decoration: none;
+ color: #262626;
+ background-color: #f5f5f5
+}
+.dropdown-menu>.active>a,
+.dropdown-menu>.active>a:focus,
+.dropdown-menu>.active>a:hover {
+ color: #fff;
+ text-decoration: none;
+ outline: 0;
+ background-color: #428bca
+}
+.dropdown-menu>.disabled>a,
+.dropdown-menu>.disabled>a:focus,
+.dropdown-menu>.disabled>a:hover {
+ color: #777
+}
+.dropdown-menu>.disabled>a:focus,
+.dropdown-menu>.disabled>a:hover {
+ text-decoration: none;
+ background-color: transparent;
+ filter: progid: DXImageTransform.Microsoft.gradient(enabled=false);
+ cursor: not-allowed
+}
+.open>.dropdown-menu {
+ display: block
+}
+.open>a {
+ outline: 0
+}
+.dropdown-menu-left {
+ left: 0;
+ right: auto
+}
+.dropdown-header {
+ font-size: 9pt;
+ color: #777
+}
+.dropdown-backdrop {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ top: 0;
+ z-index: 990
+}
+.nav-justified>.dropdown .dropdown-menu,
+.nav-tabs.nav-justified>.dropdown .dropdown-menu {
+ top: auto;
+ left: auto
+}
+.pull-right>.dropdown-menu {
+ right: 0;
+ left: auto
+}
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+ border-top: 0;
+ border-bottom: 4px solid;
+ content: ""
+}
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: 1px
+}
+@media (min-width: 768px) {
+ .navbar-right .dropdown-menu {
+ left: auto;
+ right: 0
+ }
+ .navbar-right .dropdown-menu-left {
+ left: 0;
+ right: auto
+ }
+}
+.btn-group,
+.btn-group-vertical {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle
+}
+.btn-group-vertical>.btn,
+.btn-group>.btn {
+ position: relative;
+ float: left
+}
+.btn-group-vertical>.btn.active,
+.btn-group-vertical>.btn:active,
+.btn-group-vertical>.btn:focus,
+.btn-group-vertical>.btn:hover,
+.btn-group>.btn.active,
+.btn-group>.btn:active,
+.btn-group>.btn:focus,
+.btn-group>.btn:hover {
+ z-index: 2
+}
+.btn-group-vertical>.btn:focus,
+.btn-group>.btn:focus {
+ outline: 0
+}
+.btn-group .btn+.btn,
+.btn-group .btn+.btn-group,
+.btn-group .btn-group+.btn,
+.btn-group .btn-group+.btn-group {
+ margin-left: -1px
+}
+.btn-toolbar {
+ margin-left: -5px
+}
+.btn-toolbar>.btn,
+.btn-toolbar>.btn-group,
+.btn-toolbar>.input-group {
+ margin-left: 5px
+}
+.btn .caret,
+.btn-group>.btn:first-child {
+ margin-left: 0
+}
+.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle) {
+ border-bottom-right-radius: 0;
+ border-top-right-radius: 0
+}
+.btn-group>.btn:last-child:not(:first-child),
+.btn-group>.dropdown-toggle:not(:first-child) {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0
+}
+.btn-group>.btn-group:first-child>.btn:last-child,
+.btn-group>.btn-group:first-child>.dropdown-toggle {
+ border-bottom-right-radius: 0;
+ border-top-right-radius: 0
+}
+.btn-group>.btn-group:last-child>.btn:first-child {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0
+}
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+ outline: 0
+}
+.btn-group>.btn+.dropdown-toggle {
+ padding-left: 8px;
+ padding-right: 8px
+}
+.btn-group>.btn-lg+.dropdown-toggle {
+ padding-left: 9pt;
+ padding-right: 9pt
+}
+.btn-group.open .dropdown-toggle {
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125)
+}
+.btn-group.open .dropdown-toggle.btn-link {
+ -webkit-box-shadow: none;
+ box-shadow: none
+}
+.btn-lg .caret {
+ border-width: 5px 5px 0
+}
+.dropup .btn-lg .caret {
+ border-width: 0 5px 5px
+}
+.btn-group-vertical>.btn,
+.btn-group-vertical>.btn-group,
+.btn-group-vertical>.btn-group>.btn {
+ display: block;
+ float: none;
+ width: 100%;
+ max-width: 100%
+}
+.btn-group-vertical>.btn-group>.btn {
+ float: none
+}
+.btn-group-vertical>.btn+.btn,
+.btn-group-vertical>.btn+.btn-group,
+.btn-group-vertical>.btn-group+.btn,
+.btn-group-vertical>.btn-group+.btn-group {
+ margin-top: -1px;
+ margin-left: 0
+}
+.input-group-btn:last-child>.btn,
+.input-group-btn:last-child>.btn-group,
+.input-group-btn>.btn+.btn {
+ margin-left: -1px
+}
+.btn-group-vertical>.btn:first-child:not(:last-child) {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0
+}
+.btn-group-vertical>.btn:last-child:not(:first-child) {
+ border-bottom-left-radius: 4px;
+ border-top-right-radius: 0;
+ border-top-left-radius: 0
+}
+.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn {
+ border-radius: 0
+}
+.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,
+.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0
+}
+.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child {
+ border-top-right-radius: 0;
+ border-top-left-radius: 0
+}
+.btn-group-justified {
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+ border-collapse: separate
+}
+.btn-group-justified>.btn,
+.btn-group-justified>.btn-group {
+ float: none;
+ display: table-cell;
+ width: 1%
+}
+.btn-group-justified>.btn-group .btn {
+ width: 100%
+}
+.btn-group-justified>.btn-group .dropdown-menu {
+ left: auto
+}
+[data-toggle=buttons]>.btn>input[type=checkbox],
+[data-toggle=buttons]>.btn>input[type=radio] {
+ position: absolute;
+ z-index: -1;
+ opacity: 0;
+ filter: alpha(opacity=0)
+}
+.input-group {
+ position: relative;
+ display: table;
+ border-collapse: separate
+}
+.input-group[class*=col-] {
+ float: none;
+ padding-left: 0;
+ padding-right: 0
+}
+.input-group .form-control {
+ position: relative;
+ z-index: 2;
+ float: left;
+ width: 100%;
+ margin-bottom: 0
+}
+.input-group-lg>.form-control,
+.input-group-lg>.input-group-addon,
+.input-group-lg>.input-group-btn>.btn {
+ height: 46px;
+ padding: 10px 1pc;
+ font-size: 18px;
+ line-height: 1.33;
+ border-radius: 6px
+}
+select.input-group-lg>.form-control,
+select.input-group-lg>.input-group-addon,
+select.input-group-lg>.input-group-btn>.btn {
+ height: 46px;
+ line-height: 46px
+}
+select[multiple].input-group-lg>.form-control,
+select[multiple].input-group-lg>.input-group-addon,
+select[multiple].input-group-lg>.input-group-btn>.btn,
+textarea.input-group-lg>.form-control,
+textarea.input-group-lg>.input-group-addon,
+textarea.input-group-lg>.input-group-btn>.btn {
+ height: auto
+}
+.input-group-sm>.form-control,
+.input-group-sm>.input-group-addon,
+.input-group-sm>.input-group-btn>.btn {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 9pt;
+ line-height: 1.5;
+ border-radius: 3px
+}
+select.input-group-sm>.form-control,
+select.input-group-sm>.input-group-addon,
+select.input-group-sm>.input-group-btn>.btn {
+ height: 30px;
+ line-height: 30px
+}
+select[multiple].input-group-sm>.form-control,
+select[multiple].input-group-sm>.input-group-addon,
+select[multiple].input-group-sm>.input-group-btn>.btn,
+textarea.input-group-sm>.form-control,
+textarea.input-group-sm>.input-group-addon,
+textarea.input-group-sm>.input-group-btn>.btn {
+ height: auto
+}
+.input-group .form-control,
+.input-group-addon,
+.input-group-btn {
+ display: table-cell
+}
+.nav>li,
+.nav>li>a {
+ display: block;
+ position: relative
+}
+.input-group .form-control:not(:first-child):not(:last-child),
+.input-group-addon:not(:first-child):not(:last-child),
+.input-group-btn:not(:first-child):not(:last-child) {
+ border-radius: 0
+}
+.input-group-addon,
+.input-group-btn {
+ width: 1%;
+ white-space: nowrap;
+ vertical-align: middle
+}
+.input-group-addon {
+ padding: 6px 9pt;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1;
+ color: #555;
+ text-align: center;
+ background-color: #eee;
+ border: 1px solid #ccc;
+ border-radius: 4px
+}
+.input-group-addon.input-sm {
+ padding: 5px 10px;
+ font-size: 9pt;
+ border-radius: 3px
+}
+.input-group-addon.input-lg {
+ padding: 10px 1pc;
+ font-size: 18px;
+ border-radius: 6px
+}
+.input-group-addon input[type=checkbox],
+.input-group-addon input[type=radio] {
+ margin-top: 0
+}
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child>.btn,
+.input-group-btn:first-child>.btn-group>.btn,
+.input-group-btn:first-child>.dropdown-toggle,
+.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,
+.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle) {
+ border-bottom-right-radius: 0;
+ border-top-right-radius: 0
+}
+.input-group-addon:first-child {
+ border-right: 0
+}
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,
+.input-group-btn:first-child>.btn:not(:first-child),
+.input-group-btn:last-child>.btn,
+.input-group-btn:last-child>.btn-group>.btn,
+.input-group-btn:last-child>.dropdown-toggle {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0
+}
+.input-group-addon:last-child {
+ border-left: 0
+}
+.input-group-btn {
+ position: relative;
+ font-size: 0;
+ white-space: nowrap
+}
+.input-group-btn>.btn {
+ position: relative
+}
+.input-group-btn>.btn:active,
+.input-group-btn>.btn:focus,
+.input-group-btn>.btn:hover {
+ z-index: 2
+}
+.input-group-btn:first-child>.btn,
+.input-group-btn:first-child>.btn-group {
+ margin-right: -1px
+}
+.nav {
+ margin-bottom: 0;
+ padding-left: 0;
+ list-style: none
+}
+.nav>li>a {
+ padding: 10px 15px
+}
+.nav>li>a:focus,
+.nav>li>a:hover {
+ text-decoration: none;
+ background-color: #eee
+}
+.nav>li.disabled>a {
+ color: #777
+}
+.nav>li.disabled>a:focus,
+.nav>li.disabled>a:hover {
+ color: #777;
+ text-decoration: none;
+ background-color: transparent;
+ cursor: not-allowed
+}
+.nav .open>a,
+.nav .open>a:focus,
+.nav .open>a:hover {
+ background-color: #eee;
+ border-color: #428bca
+}
+.nav .nav-divider {
+ height: 1px;
+ margin: 9px 0;
+ overflow: hidden;
+ background-color: #e5e5e5
+}
+.nav>li>a>img {
+ max-width: none
+}
+.nav-tabs {
+ border-bottom: 1px solid #ddd
+}
+.nav-tabs>li {
+ float: left;
+ margin-bottom: -1px
+}
+.nav-tabs>li>a {
+ margin-right: 2px;
+ line-height: 1.42857143;
+ border: 1px solid transparent;
+ border-radius: 4px 4px 0 0
+}
+.nav-tabs>li>a:hover {
+ border-color: #eee #eee #ddd
+}
+.nav-tabs>li.active>a,
+.nav-tabs>li.active>a:focus,
+.nav-tabs>li.active>a:hover {
+ color: #555;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+ cursor: default
+}
+.nav-tabs.nav-justified {
+ width: 100%;
+ border-bottom: 0
+}
+.nav-tabs.nav-justified>li {
+ float: none
+}
+.nav-tabs.nav-justified>li>a {
+ text-align: center;
+ margin-bottom: 5px;
+ margin-right: 0;
+ border-radius: 4px
+}
+.nav-tabs.nav-justified>.active>a,
+.nav-tabs.nav-justified>.active>a:focus,
+.nav-tabs.nav-justified>.active>a:hover {
+ border: 1px solid #ddd
+}
+@media (min-width: 768px) {
+ .nav-tabs.nav-justified>li {
+ display: table-cell;
+ width: 1%
+ }
+ .nav-tabs.nav-justified>li>a {
+ margin-bottom: 0;
+ border-bottom: 1px solid #ddd;
+ border-radius: 4px 4px 0 0
+ }
+ .nav-tabs.nav-justified>.active>a,
+ .nav-tabs.nav-justified>.active>a:focus,
+ .nav-tabs.nav-justified>.active>a:hover {
+ border-bottom-color: #fff
+ }
+}
+.nav-pills>li {
+ float: left
+}
+.nav-justified>li,
+.nav-stacked>li {
+ float: none
+}
+.nav-pills>li>a {
+ border-radius: 4px
+}
+.nav-pills>li+li {
+ margin-left: 2px
+}
+.nav-pills>li.active>a,
+.nav-pills>li.active>a:focus,
+.nav-pills>li.active>a:hover {
+ color: #fff;
+ background-color: #428bca
+}
+.nav-stacked>li+li {
+ margin-top: 2px;
+ margin-left: 0
+}
+.nav-justified {
+ width: 100%
+}
+.nav-justified>li>a {
+ text-align: center;
+ margin-bottom: 5px
+}
+.nav-tabs-justified {
+ border-bottom: 0
+}
+.nav-tabs-justified>li>a {
+ margin-right: 0;
+ border-radius: 4px
+}
+.nav-tabs-justified>.active>a,
+.nav-tabs-justified>.active>a:focus,
+.nav-tabs-justified>.active>a:hover {
+ border: 1px solid #ddd
+}
+@media (min-width: 768px) {
+ .nav-justified>li {
+ display: table-cell;
+ width: 1%
+ }
+ .nav-justified>li>a {
+ margin-bottom: 0
+ }
+ .nav-tabs-justified>li>a {
+ border-bottom: 1px solid #ddd;
+ border-radius: 4px 4px 0 0
+ }
+ .nav-tabs-justified>.active>a,
+ .nav-tabs-justified>.active>a:focus,
+ .nav-tabs-justified>.active>a:hover {
+ border-bottom-color: #fff
+ }
+}
+.tab-content>.tab-pane {
+ display: none
+}
+.tab-content>.active {
+ display: block
+}
+.nav-tabs .dropdown-menu {
+ margin-top: -1px;
+ border-top-right-radius: 0;
+ border-top-left-radius: 0
+}
+.navbar {
+ position: relative;
+ min-height: 50px;
+ margin-bottom: 20px;
+ border: 1px solid transparent
+}
+.navbar-collapse {
+ overflow-x: visible;
+ padding-right: 15px;
+ padding-left: 15px;
+ border-top: 1px solid transparent;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
+ -webkit-overflow-scrolling: touch
+}
+.navbar-collapse.in {
+ overflow-y: auto
+}
+@media (min-width: 768px) {
+ .navbar {
+ border-radius: 4px
+ }
+ .navbar-header {
+ float: left
+ }
+ .navbar-collapse {
+ width: auto;
+ border-top: 0;
+ box-shadow: none
+ }
+ .navbar-collapse.collapse {
+ display: block!important;
+ height: auto!important;
+ padding-bottom: 0;
+ overflow: visible!important
+ }
+ .navbar-collapse.in {
+ overflow-y: visible
+ }
+ .navbar-fixed-bottom .navbar-collapse,
+ .navbar-fixed-top .navbar-collapse,
+ .navbar-static-top .navbar-collapse {
+ padding-left: 0;
+ padding-right: 0
+ }
+}
+.carousel-inner,
+.embed-responsive,
+.modal,
+.modal-open,
+.progress {
+ overflow: hidden
+}
+@media (max-width: 480px) and (orientation: landscape) {
+ .navbar-fixed-bottom .navbar-collapse,
+ .navbar-fixed-top .navbar-collapse {
+ max-height: 200px
+ }
+}
+.container-fluid>.navbar-collapse,
+.container-fluid>.navbar-header,
+.container>.navbar-collapse,
+.container>.navbar-header {
+ margin-right: -15px;
+ margin-left: -15px
+}
+.navbar-static-top {
+ z-index: 1000;
+ border-width: 0 0 1px
+}
+.navbar-fixed-bottom,
+.navbar-fixed-top {
+ position: fixed;
+ right: 0;
+ left: 0;
+ z-index: 1030;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0)
+}
+.navbar-fixed-top {
+ top: 0;
+ border-width: 0 0 1px
+}
+.navbar-fixed-bottom {
+ bottom: 0;
+ margin-bottom: 0;
+ border-width: 1px 0 0
+}
+.navbar-brand {
+ float: left;
+ padding: 15px;
+ font-size: 18px;
+ line-height: 20px;
+ height: 50px
+}
+.navbar-brand:focus,
+.navbar-brand:hover {
+ text-decoration: none
+}
+@media (min-width: 768px) {
+ .container-fluid>.navbar-collapse,
+ .container-fluid>.navbar-header,
+ .container>.navbar-collapse,
+ .container>.navbar-header {
+ margin-right: 0;
+ margin-left: 0
+ }
+ .navbar-fixed-bottom,
+ .navbar-fixed-top,
+ .navbar-static-top {
+ border-radius: 0
+ }
+ .navbar>.container .navbar-brand,
+ .navbar>.container-fluid .navbar-brand {
+ margin-left: -15px
+ }
+}
+.navbar-toggle {
+ position: relative;
+ float: right;
+ margin-right: 15px;
+ padding: 9px 10px;
+ margin-top: 8px;
+ margin-bottom: 8px;
+ background-color: transparent;
+ border: 1px solid transparent;
+ border-radius: 4px
+}
+.navbar-toggle:focus {
+ outline: 0
+}
+.navbar-toggle .icon-bar {
+ display: block;
+ width: 22px;
+ height: 2px;
+ border-radius: 1px
+}
+.navbar-toggle .icon-bar+.icon-bar {
+ margin-top: 4px
+}
+.navbar-nav {
+ margin: 7.5px -15px
+}
+.navbar-nav>li>a {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ line-height: 20px
+}
+@media (max-width: 767px) {
+ .navbar-nav .open .dropdown-menu {
+ position: static;
+ float: none;
+ width: auto;
+ margin-top: 0;
+ background-color: transparent;
+ border: 0;
+ box-shadow: none
+ }
+ .navbar-nav .open .dropdown-menu .dropdown-header,
+ .navbar-nav .open .dropdown-menu>li>a {
+ padding: 5px 15px 5px 25px
+ }
+ .navbar-nav .open .dropdown-menu>li>a {
+ line-height: 20px
+ }
+ .navbar-nav .open .dropdown-menu>li>a:focus,
+ .navbar-nav .open .dropdown-menu>li>a:hover {
+ background-image: none
+ }
+}
+.progress-bar-striped,
+.progress-striped .progress-bar,
+.progress-striped .progress-bar-success {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent)
+}
+@media (min-width: 768px) {
+ .navbar-toggle {
+ display: none
+ }
+ .navbar-nav {
+ float: left;
+ margin: 0
+ }
+ .navbar-nav>li {
+ float: left
+ }
+ .navbar-nav>li>a {
+ padding-top: 15px;
+ padding-bottom: 15px
+ }
+ .navbar-nav.navbar-right:last-child {
+ margin-right: -15px
+ }
+ .navbar-left {
+ float: left!important
+ }
+ .navbar-right {
+ float: right!important
+ }
+}
+.navbar-form {
+ padding: 10px 15px;
+ border-top: 1px solid transparent;
+ border-bottom: 1px solid transparent;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
+ margin: 8px -15px
+}
+@media (min-width: 768px) {
+ .navbar-form .control-label,
+ .navbar-form .form-group {
+ margin-bottom: 0;
+ vertical-align: middle
+ }
+ .navbar-form .form-group {
+ display: inline-block
+ }
+ .navbar-form .form-control {
+ display: inline-block;
+ width: auto;
+ vertical-align: middle
+ }
+ .navbar-form .input-group {
+ display: inline-table;
+ vertical-align: middle
+ }
+ .navbar-form .input-group .form-control,
+ .navbar-form .input-group .input-group-addon,
+ .navbar-form .input-group .input-group-btn {
+ width: auto
+ }
+ .navbar-form .input-group>.form-control {
+ width: 100%
+ }
+ .navbar-form .checkbox,
+ .navbar-form .radio {
+ display: inline-block;
+ margin-top: 0;
+ margin-bottom: 0;
+ vertical-align: middle
+ }
+ .navbar-form .checkbox label,
+ .navbar-form .radio label {
+ padding-left: 0
+ }
+ .navbar-form .checkbox input[type=checkbox],
+ .navbar-form .radio input[type=radio] {
+ position: relative;
+ margin-left: 0
+ }
+ .navbar-form .has-feedback .form-control-feedback {
+ top: 0
+ }
+ .navbar-form {
+ width: auto;
+ border: 0;
+ margin-left: 0;
+ margin-right: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none
+ }
+ .navbar-form.navbar-right:last-child {
+ margin-right: -15px
+ }
+}
+.breadcrumb>li,
+.pagination {
+ display: inline-block
+}
+@media (max-width: 767px) {
+ .navbar-form .form-group {
+ margin-bottom: 5px
+ }
+}
+.navbar-nav>li>.dropdown-menu {
+ margin-top: 0;
+ border-top-right-radius: 0;
+ border-top-left-radius: 0
+}
+.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0
+}
+.navbar-btn {
+ margin-top: 8px;
+ margin-bottom: 8px
+}
+.navbar-btn.btn-sm {
+ margin-top: 10px;
+ margin-bottom: 10px
+}
+.navbar-btn.btn-xs {
+ margin-top: 14px;
+ margin-bottom: 14px
+}
+.navbar-text {
+ margin-top: 15px;
+ margin-bottom: 15px
+}
+@media (min-width: 768px) {
+ .navbar-text {
+ float: left;
+ margin-left: 15px;
+ margin-right: 15px
+ }
+ .navbar-text.navbar-right:last-child {
+ margin-right: 0
+ }
+}
+.navbar-default {
+ background-color: #f8f8f8;
+ border-color: #e7e7e7
+}
+.navbar-default .navbar-brand {
+ color: #777
+}
+.navbar-default .navbar-brand:focus,
+.navbar-default .navbar-brand:hover {
+ color: #5e5e5e;
+ background-color: transparent
+}
+.navbar-default .navbar-nav>li>a,
+.navbar-default .navbar-text {
+ color: #777
+}
+.navbar-default .navbar-nav>li>a:focus,
+.navbar-default .navbar-nav>li>a:hover {
+ color: #333;
+ background-color: transparent
+}
+.navbar-default .navbar-nav>.active>a,
+.navbar-default .navbar-nav>.active>a:focus,
+.navbar-default .navbar-nav>.active>a:hover {
+ color: #555;
+ background-color: #e7e7e7
+}
+.navbar-default .navbar-nav>.disabled>a,
+.navbar-default .navbar-nav>.disabled>a:focus,
+.navbar-default .navbar-nav>.disabled>a:hover {
+ color: #ccc;
+ background-color: transparent
+}
+.navbar-default .navbar-toggle {
+ border-color: #ddd
+}
+.navbar-default .navbar-toggle:focus,
+.navbar-default .navbar-toggle:hover {
+ background-color: #ddd
+}
+.navbar-default .navbar-toggle .icon-bar {
+ background-color: #888
+}
+.navbar-default .navbar-collapse,
+.navbar-default .navbar-form {
+ border-color: #e7e7e7
+}
+.navbar-default .navbar-nav>.open>a,
+.navbar-default .navbar-nav>.open>a:focus,
+.navbar-default .navbar-nav>.open>a:hover {
+ background-color: #e7e7e7;
+ color: #555
+}
+@media (max-width: 767px) {
+ .navbar-default .navbar-nav .open .dropdown-menu>li>a {
+ color: #777
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,
+ .navbar-default .navbar-nav .open .dropdown-menu>li>a:hover {
+ color: #333;
+ background-color: transparent
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu>.active>a,
+ .navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,
+ .navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover {
+ color: #555;
+ background-color: #e7e7e7
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,
+ .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,
+ .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover {
+ color: #ccc;
+ background-color: transparent
+ }
+}
+.navbar-default .navbar-link {
+ color: #777
+}
+.navbar-default .navbar-link:hover {
+ color: #333
+}
+.navbar-default .btn-link {
+ color: #777
+}
+.navbar-default .btn-link:focus,
+.navbar-default .btn-link:hover {
+ color: #333
+}
+.navbar-default .btn-link[disabled]:focus,
+.navbar-default .btn-link[disabled]:hover,
+fieldset[disabled] .navbar-default .btn-link:focus,
+fieldset[disabled] .navbar-default .btn-link:hover {
+ color: #ccc
+}
+.navbar-inverse {
+ background-color: #222;
+ border-color: #080808
+}
+.navbar-inverse .navbar-brand {
+ color: #777
+}
+.navbar-inverse .navbar-brand:focus,
+.navbar-inverse .navbar-brand:hover {
+ color: #fff;
+ background-color: transparent
+}
+.navbar-inverse .navbar-nav>li>a,
+.navbar-inverse .navbar-text {
+ color: #777
+}
+.navbar-inverse .navbar-nav>li>a:focus,
+.navbar-inverse .navbar-nav>li>a:hover {
+ color: #fff;
+ background-color: transparent
+}
+.navbar-inverse .navbar-nav>.active>a,
+.navbar-inverse .navbar-nav>.active>a:focus,
+.navbar-inverse .navbar-nav>.active>a:hover {
+ color: #fff;
+ background-color: #080808
+}
+.navbar-inverse .navbar-nav>.disabled>a,
+.navbar-inverse .navbar-nav>.disabled>a:focus,
+.navbar-inverse .navbar-nav>.disabled>a:hover {
+ color: #444;
+ background-color: transparent
+}
+.navbar-inverse .navbar-toggle {
+ border-color: #333
+}
+.navbar-inverse .navbar-toggle:focus,
+.navbar-inverse .navbar-toggle:hover {
+ background-color: #333
+}
+.navbar-inverse .navbar-toggle .icon-bar {
+ background-color: #fff
+}
+.navbar-inverse .navbar-collapse,
+.navbar-inverse .navbar-form {
+ border-color: #101010
+}
+.navbar-inverse .navbar-nav>.open>a,
+.navbar-inverse .navbar-nav>.open>a:focus,
+.navbar-inverse .navbar-nav>.open>a:hover {
+ background-color: #080808;
+ color: #fff
+}
+@media (max-width: 767px) {
+ .navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header {
+ border-color: #080808
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu .divider {
+ background-color: #080808
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu>li>a {
+ color: #777
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,
+ .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover {
+ color: #fff;
+ background-color: transparent
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,
+ .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,
+ .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover {
+ color: #fff;
+ background-color: #080808
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,
+ .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,
+ .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover {
+ color: #444;
+ background-color: transparent
+ }
+}
+.navbar-inverse .navbar-link {
+ color: #777
+}
+.navbar-inverse .navbar-link:hover {
+ color: #fff
+}
+.navbar-inverse .btn-link {
+ color: #777
+}
+.navbar-inverse .btn-link:focus,
+.navbar-inverse .btn-link:hover {
+ color: #fff
+}
+.navbar-inverse .btn-link[disabled]:focus,
+.navbar-inverse .btn-link[disabled]:hover,
+fieldset[disabled] .navbar-inverse .btn-link:focus,
+fieldset[disabled] .navbar-inverse .btn-link:hover {
+ color: #444
+}
+.breadcrumb {
+ padding: 8px 15px;
+ margin-bottom: 20px;
+ list-style: none;
+ background-color: #f5f5f5;
+ border-radius: 4px
+}
+.breadcrumb>li+li:before {
+ content: "/\00a0";
+ padding: 0 5px;
+ color: #ccc
+}
+.breadcrumb>.active {
+ color: #777
+}
+.pagination {
+ padding-left: 0;
+ margin: 20px 0;
+ border-radius: 4px
+}
+.pager li,
+.pagination>li {
+ display: inline
+}
+.pagination>li>a,
+.pagination>li>span {
+ position: relative;
+ float: left;
+ padding: 6px 9pt;
+ line-height: 1.0;
+ text-decoration: none;
+ color: #428bca;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ margin-left: -1px
+}
+.badge,
+.label {
+ font-weight: 700;
+ line-height: 1;
+ vertical-align: baseline;
+ white-space: nowrap;
+ text-align: center
+}
+.pagination>li:first-child>a,
+.pagination>li:first-child>span {
+ margin-left: 0;
+ border-bottom-left-radius: 4px;
+ border-top-left-radius: 4px
+}
+.pagination>li:last-child>a,
+.pagination>li:last-child>span {
+ border-bottom-right-radius: 4px;
+ border-top-right-radius: 4px
+}
+.pagination>li>a:focus,
+.pagination>li>a:hover,
+.pagination>li>span:focus,
+.pagination>li>span:hover {
+ color: #2a6496;
+ background-color: #eee;
+ border-color: #ddd
+}
+.pagination>.active>a,
+.pagination>.active>a:focus,
+.pagination>.active>a:hover,
+.pagination>.active>span,
+.pagination>.active>span:focus,
+.pagination>.active>span:hover {
+ z-index: 2;
+ color: #fff;
+ background-color: #428bca;
+ border-color: #428bca;
+ cursor: default
+}
+.pagination>.disabled>a,
+.pagination>.disabled>a:focus,
+.pagination>.disabled>a:hover,
+.pagination>.disabled>span,
+.pagination>.disabled>span:focus,
+.pagination>.disabled>span:hover {
+ color: #777;
+ background-color: #fff;
+ border-color: #ddd;
+ cursor: not-allowed
+}
+.pagination-lg>li>a,
+.pagination-lg>li>span {
+ padding: 10px 1pc;
+ font-size: 18px
+}
+.pagination-lg>li:first-child>a,
+.pagination-lg>li:first-child>span {
+ border-bottom-left-radius: 6px;
+ border-top-left-radius: 6px
+}
+.pagination-lg>li:last-child>a,
+.pagination-lg>li:last-child>span {
+ border-bottom-right-radius: 6px;
+ border-top-right-radius: 6px
+}
+.pagination-sm>li>a,
+.pagination-sm>li>span {
+ padding: 5px 10px;
+ font-size: 9pt
+}
+.pagination-sm>li:first-child>a,
+.pagination-sm>li:first-child>span {
+ border-bottom-left-radius: 3px;
+ border-top-left-radius: 3px
+}
+.pagination-sm>li:last-child>a,
+.pagination-sm>li:last-child>span {
+ border-bottom-right-radius: 3px;
+ border-top-right-radius: 3px
+}
+.pager {
+ padding-left: 0;
+ margin: 20px 0;
+ list-style: none;
+ text-align: center
+}
+.pager li>a,
+.pager li>span {
+ display: inline-block;
+ padding: 5px 14px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 15px
+}
+.pager li>a:focus,
+.pager li>a:hover {
+ text-decoration: none;
+ background-color: #eee
+}
+.pager .next>a,
+.pager .next>span {
+ float: right
+}
+.pager .previous>a,
+.pager .previous>span {
+ float: left
+}
+.pager .disabled>a,
+.pager .disabled>a:focus,
+.pager .disabled>a:hover,
+.pager .disabled>span {
+ color: #777;
+ background-color: #fff;
+ cursor: not-allowed
+}
+.label {
+ display: inline;
+ padding: .2em .6em .3em;
+ font-size: 75%;
+ color: #fff;
+ border-radius: .25em
+}
+a.label:focus,
+a.label:hover {
+ color: #fff;
+ text-decoration: none;
+ cursor: pointer
+}
+.label:empty {
+ display: none
+}
+.btn .label {
+ position: relative;
+ top: -1px
+}
+.label-default {
+ background-color: #777
+}
+.label-default[href]:focus,
+.label-default[href]:hover {
+ background-color: #5e5e5e
+}
+.label-primary {
+ background-color: #428bca
+}
+.label-primary[href]:focus,
+.label-primary[href]:hover {
+ background-color: #3071a9
+}
+.label-success {
+ background-color: #5cb85c
+}
+.label-success[href]:focus,
+.label-success[href]:hover {
+ background-color: #449d44
+}
+.label-info {
+ background-color: #5bc0de
+}
+.label-info[href]:focus,
+.label-info[href]:hover {
+ background-color: #31b0d5
+}
+.label-warning {
+ background-color: #f0ad4e
+}
+.label-warning[href]:focus,
+.label-warning[href]:hover {
+ background-color: #ec971f
+}
+.label-danger {
+ background-color: #d9534f
+}
+.label-danger[href]:focus,
+.label-danger[href]:hover {
+ background-color: #c9302c
+}
+.badge {
+ display: inline-block;
+ min-width: 10px;
+ padding: 3px 7px;
+ font-size: 9pt;
+ color: #fff;
+ background-color: #777;
+ border-radius: 10px
+}
+.badge:empty {
+ display: none
+}
+.list-group-item,
+.media-object,
+.thumbnail {
+ display: block
+}
+.btn .badge {
+ position: relative;
+ top: -1px
+}
+.btn-xs .badge {
+ top: 0;
+ padding: 1px 5px
+}
+a.badge:focus,
+a.badge:hover {
+ color: #fff;
+ text-decoration: none;
+ cursor: pointer
+}
+.nav-pills>.active>a>.badge,
+a.list-group-item.active>.badge {
+ color: #428bca;
+ background-color: #fff
+}
+.jumbotron,
+.jumbotron .h1,
+.jumbotron h1 {
+ color: inherit
+}
+.nav-pills>li>a>.badge {
+ margin-left: 3px
+}
+.jumbotron {
+ padding: 30px;
+ margin-bottom: 30px;
+ background-color: #eee
+}
+.jumbotron p {
+ margin-bottom: 15px;
+ font-size: 21px;
+ font-weight: 200
+}
+.alert,
+.thumbnail {
+ margin-bottom: 20px
+}
+.alert .alert-link,
+.close {
+ font-weight: 700
+}
+.jumbotron>hr {
+ border-top-color: #d5d5d5
+}
+.container .jumbotron {
+ border-radius: 6px
+}
+.jumbotron .container {
+ max-width: 100%
+}
+@media screen and (min-width: 768px) {
+ .jumbotron {
+ padding-top: 3pc;
+ padding-bottom: 3pc
+ }
+ .container .jumbotron {
+ padding-left: 60px;
+ padding-right: 60px
+ }
+ .jumbotron .h1,
+ .jumbotron h1 {
+ font-size: 63px
+ }
+}
+.thumbnail {
+ padding: 4px;
+ line-height: 1.42857143;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ -o-transition: all .2s ease-in-out;
+ transition: all .2s ease-in-out
+}
+.thumbnail a>img,
+.thumbnail>img {
+ margin-left: auto;
+ margin-right: auto
+}
+a.thumbnail.active,
+a.thumbnail:focus,
+a.thumbnail:hover {
+ border-color: #428bca
+}
+.thumbnail .caption {
+ padding: 9px;
+ color: #333
+}
+.alert {
+ padding: 15px;
+ border: 1px solid transparent;
+ border-radius: 4px
+}
+.alert h4 {
+ margin-top: 0;
+ color: inherit
+}
+.alert>p,
+.alert>ul {
+ margin-bottom: 0
+}
+.alert>p+p {
+ margin-top: 5px
+}
+.alert-dismissable,
+.alert-dismissible {
+ padding-right: 35px
+}
+.alert-dismissable .close,
+.alert-dismissible .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ color: inherit
+}
+.modal,
+.modal-backdrop {
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0
+}
+.alert-success {
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+ color: #3c763d
+}
+.alert-success hr {
+ border-top-color: #c9e2b3
+}
+.alert-success .alert-link {
+ color: #2b542c
+}
+.alert-info {
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+ color: #31708f
+}
+.alert-info hr {
+ border-top-color: #a6e1ec
+}
+.alert-info .alert-link {
+ color: #245269
+}
+.alert-warning {
+ background-color: #fcf8e3;
+ border-color: #faebcc;
+ color: #8a6d3b
+}
+.alert-warning hr {
+ border-top-color: #f7e1b5
+}
+.alert-warning .alert-link {
+ color: #66512c
+}
+.alert-danger {
+ background-color: #f2dede;
+ border-color: #ebccd1;
+ color: #a94442
+}
+.alert-danger hr {
+ border-top-color: #e4b9c0
+}
+.alert-danger .alert-link {
+ color: #843534
+}
+@-webkit-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0
+ }
+ to {
+ background-position: 0 0
+ }
+}
+@keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0
+ }
+ to {
+ background-position: 0 0
+ }
+}
+.progress {
+ height: 20px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1)
+}
+.progress-bar {
+ float: left;
+ width: 0;
+ height: 100%;
+ font-size: 9pt;
+ line-height: 20px;
+ color: #fff;
+ text-align: center;
+ background-color: #428bca;
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+ -webkit-transition: width .6s ease;
+ -o-transition: width .6s ease;
+ transition: width .6s ease
+}
+.close,
+.list-group-item>.badge {
+ float: right
+}
+.progress-bar-striped,
+.progress-striped .progress-bar {
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-size: 40px 40px
+}
+.progress-bar.active,
+.progress.active .progress-bar {
+ -webkit-animation: progress-bar-stripes 2s linear infinite;
+ -o-animation: progress-bar-stripes 2s linear infinite;
+ animation: progress-bar-stripes 2s linear infinite
+}
+.progress-bar[aria-valuenow="1"],
+.progress-bar[aria-valuenow="2"] {
+ min-width: 30px
+}
+.progress-bar[aria-valuenow="0"] {
+ color: #777;
+ min-width: 30px;
+ background-color: transparent;
+ background-image: none;
+ box-shadow: none
+}
+.progress-bar-success {
+ background-color: #5cb85c
+}
+.progress-striped .progress-bar-success {
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent)
+}
+.progress-striped .progress-bar-info,
+.progress-striped .progress-bar-warning {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent)
+}
+.progress-bar-info {
+ background-color: #5bc0de
+}
+.progress-striped .progress-bar-info {
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent)
+}
+.progress-bar-warning {
+ background-color: #f0ad4e
+}
+.progress-striped .progress-bar-warning {
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent)
+}
+.progress-bar-danger {
+ background-color: #d9534f
+}
+.progress-striped .progress-bar-danger {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent)
+}
+.media,
+.media-body {
+ overflow: hidden;
+ zoom: 1
+}
+.media,
+.media .media {
+ margin-top: 15px
+}
+.media:first-child {
+ margin-top: 0
+}
+.media-heading {
+ margin: 0 0 5px
+}
+.media>.pull-left {
+ margin-right: 10px
+}
+.media>.pull-right {
+ margin-left: 10px
+}
+.media-list {
+ padding-left: 0;
+ list-style: none
+}
+.list-group {
+ margin-bottom: 20px;
+ padding-left: 0
+}
+.list-group-item {
+ position: relative;
+ padding: 10px 15px;
+ margin-bottom: -1px;
+ background-color: #fff;
+ border: 1px solid #ddd
+}
+.list-group-item:first-child {
+ border-top-right-radius: 4px;
+ border-top-left-radius: 4px
+}
+.list-group-item:last-child {
+ margin-bottom: 0;
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px
+}
+.list-group-item>.badge+.badge {
+ margin-right: 5px
+}
+a.list-group-item {
+ color: #555
+}
+a.list-group-item .list-group-item-heading {
+ color: #333
+}
+a.list-group-item:focus,
+a.list-group-item:hover {
+ text-decoration: none;
+ color: #555;
+ background-color: #f5f5f5
+}
+.list-group-item.disabled,
+.list-group-item.disabled:focus,
+.list-group-item.disabled:hover {
+ background-color: #eee;
+ color: #777
+}
+.list-group-item.disabled .list-group-item-heading,
+.list-group-item.disabled:focus .list-group-item-heading,
+.list-group-item.disabled:hover .list-group-item-heading {
+ color: inherit
+}
+.list-group-item.disabled .list-group-item-text,
+.list-group-item.disabled:focus .list-group-item-text,
+.list-group-item.disabled:hover .list-group-item-text {
+ color: #777
+}
+.list-group-item.active,
+.list-group-item.active:focus,
+.list-group-item.active:hover {
+ z-index: 2;
+ color: #fff;
+ background-color: #428bca;
+ border-color: #428bca
+}
+.list-group-item.active .list-group-item-heading,
+.list-group-item.active .list-group-item-heading>.small,
+.list-group-item.active .list-group-item-heading>small,
+.list-group-item.active:focus .list-group-item-heading,
+.list-group-item.active:focus .list-group-item-heading>.small,
+.list-group-item.active:focus .list-group-item-heading>small,
+.list-group-item.active:hover .list-group-item-heading,
+.list-group-item.active:hover .list-group-item-heading>.small,
+.list-group-item.active:hover .list-group-item-heading>small {
+ color: inherit
+}
+.list-group-item.active .list-group-item-text,
+.list-group-item.active:focus .list-group-item-text,
+.list-group-item.active:hover .list-group-item-text {
+ color: #e1edf7
+}
+.list-group-item-success {
+ color: #3c763d;
+ background-color: #dff0d8
+}
+a.list-group-item-success {
+ color: #3c763d
+}
+a.list-group-item-success .list-group-item-heading {
+ color: inherit
+}
+a.list-group-item-success:focus,
+a.list-group-item-success:hover {
+ color: #3c763d;
+ background-color: #d0e9c6
+}
+a.list-group-item-success.active,
+a.list-group-item-success.active:focus,
+a.list-group-item-success.active:hover {
+ color: #fff;
+ background-color: #3c763d;
+ border-color: #3c763d
+}
+.list-group-item-info {
+ color: #31708f;
+ background-color: #d9edf7
+}
+a.list-group-item-info {
+ color: #31708f
+}
+a.list-group-item-info .list-group-item-heading {
+ color: inherit
+}
+a.list-group-item-info:focus,
+a.list-group-item-info:hover {
+ color: #31708f;
+ background-color: #c4e3f3
+}
+a.list-group-item-info.active,
+a.list-group-item-info.active:focus,
+a.list-group-item-info.active:hover {
+ color: #fff;
+ background-color: #31708f;
+ border-color: #31708f
+}
+.list-group-item-warning {
+ color: #8a6d3b;
+ background-color: #fcf8e3
+}
+a.list-group-item-warning {
+ color: #8a6d3b
+}
+a.list-group-item-warning .list-group-item-heading {
+ color: inherit
+}
+a.list-group-item-warning:focus,
+a.list-group-item-warning:hover {
+ color: #8a6d3b;
+ background-color: #faf2cc
+}
+a.list-group-item-warning.active,
+a.list-group-item-warning.active:focus,
+a.list-group-item-warning.active:hover {
+ color: #fff;
+ background-color: #8a6d3b;
+ border-color: #8a6d3b
+}
+.list-group-item-danger {
+ color: #a94442;
+ background-color: #f2dede
+}
+a.list-group-item-danger {
+ color: #a94442
+}
+a.list-group-item-danger .list-group-item-heading {
+ color: inherit
+}
+a.list-group-item-danger:focus,
+a.list-group-item-danger:hover {
+ color: #a94442;
+ background-color: #ebcccc
+}
+a.list-group-item-danger.active,
+a.list-group-item-danger.active:focus,
+a.list-group-item-danger.active:hover {
+ color: #fff;
+ background-color: #a94442;
+ border-color: #a94442
+}
+.panel-heading>.dropdown .dropdown-toggle,
+.panel-title,
+.panel-title>a {
+ color: inherit
+}
+.list-group-item-heading {
+ margin-top: 0;
+ margin-bottom: 5px
+}
+.list-group-item-text {
+ margin-bottom: 0;
+ line-height: 1.3
+}
+.panel {
+ margin-bottom: 20px;
+ background-color: #fff;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+ box-shadow: 0 1px 1px rgba(0, 0, 0, .05)
+}
+.panel-title,
+.panel>.list-group,
+.panel>.panel-collapse>.table,
+.panel>.table,
+.panel>.table-responsive>.table {
+ margin-bottom: 0
+}
+.panel-body {
+ padding: 15px
+}
+.panel-heading {
+ padding: 10px 15px;
+ border-bottom: 1px solid transparent;
+ border-top-right-radius: 3px;
+ border-top-left-radius: 3px
+}
+.panel-group .panel-heading,
+.panel>.list-group:last-child .list-group-item:last-child,
+.panel>.table-bordered>tbody>tr:first-child>td,
+.panel>.table-bordered>tbody>tr:first-child>th,
+.panel>.table-bordered>tbody>tr:last-child>td,
+.panel>.table-bordered>tbody>tr:last-child>th,
+.panel>.table-bordered>tfoot>tr:last-child>td,
+.panel>.table-bordered>tfoot>tr:last-child>th,
+.panel>.table-bordered>thead>tr:first-child>td,
+.panel>.table-bordered>thead>tr:first-child>th,
+.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,
+.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,
+.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,
+.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,
+.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,
+.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,
+.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,
+.panel>.table-responsive>.table-bordered>thead>tr:first-child>th {
+ border-bottom: 0
+}
+.panel-title {
+ margin-top: 0;
+ font-size: 1pc
+}
+.panel-footer {
+ padding: 10px 15px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px
+}
+.panel>.list-group .list-group-item {
+ border-width: 1px 0;
+ border-radius: 0
+}
+.panel>.list-group:last-child .list-group-item:last-child,
+.panel>.table-responsive:last-child>.table:last-child,
+.panel>.table:last-child {
+ border-bottom-left-radius: 3px;
+ border-bottom-right-radius: 3px
+}
+.panel>.list-group:first-child .list-group-item:first-child {
+ border-top: 0;
+ border-top-right-radius: 3px;
+ border-top-left-radius: 3px
+}
+.list-group+.panel-footer,
+.panel-heading+.list-group .list-group-item:first-child {
+ border-top-width: 0
+}
+.panel>.table-responsive:first-child>.table:first-child,
+.panel>.table:first-child {
+ border-top-right-radius: 3px;
+ border-top-left-radius: 3px
+}
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,
+.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,
+.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,
+.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,
+.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,
+.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,
+.panel>.table:first-child>thead:first-child>tr:first-child th:first-child {
+ border-top-left-radius: 3px
+}
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,
+.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,
+.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,
+.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,
+.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,
+.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,
+.panel>.table:first-child>thead:first-child>tr:first-child th:last-child {
+ border-top-right-radius: 3px
+}
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,
+.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,
+.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,
+.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,
+.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,
+.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,
+.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child {
+ border-bottom-left-radius: 3px
+}
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,
+.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,
+.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,
+.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,
+.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,
+.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,
+.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child {
+ border-bottom-right-radius: 3px
+}
+.panel>.panel-body+.table,
+.panel>.panel-body+.table-responsive {
+ border-top: 1px solid #ddd
+}
+.panel>.table>tbody:first-child>tr:first-child td,
+.panel>.table>tbody:first-child>tr:first-child th {
+ border-top: 0
+}
+.panel>.table-bordered,
+.panel>.table-responsive>.table-bordered {
+ border: 0
+}
+.panel>.table-bordered>tbody>tr>td:first-child,
+.panel>.table-bordered>tbody>tr>th:first-child,
+.panel>.table-bordered>tfoot>tr>td:first-child,
+.panel>.table-bordered>tfoot>tr>th:first-child,
+.panel>.table-bordered>thead>tr>td:first-child,
+.panel>.table-bordered>thead>tr>th:first-child,
+.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,
+.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,
+.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,
+.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,
+.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,
+.panel>.table-responsive>.table-bordered>thead>tr>th:first-child {
+ border-left: 0
+}
+.panel>.table-bordered>tbody>tr>td:last-child,
+.panel>.table-bordered>tbody>tr>th:last-child,
+.panel>.table-bordered>tfoot>tr>td:last-child,
+.panel>.table-bordered>tfoot>tr>th:last-child,
+.panel>.table-bordered>thead>tr>td:last-child,
+.panel>.table-bordered>thead>tr>th:last-child,
+.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,
+.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,
+.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,
+.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,
+.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,
+.panel>.table-responsive>.table-bordered>thead>tr>th:last-child {
+ border-right: 0
+}
+.panel>.table-responsive {
+ border: 0;
+ margin-bottom: 0
+}
+.panel-group {
+ margin-bottom: 20px
+}
+.panel-group .panel {
+ margin-bottom: 0;
+ border-radius: 4px
+}
+.panel-group .panel+.panel {
+ margin-top: 5px
+}
+.panel-group .panel-heading+.panel-collapse>.panel-body {
+ border-top: 1px solid #ddd
+}
+.panel-group .panel-footer {
+ border-top: 0
+}
+.panel-group .panel-footer+.panel-collapse .panel-body {
+ border-bottom: 1px solid #ddd
+}
+.panel-default {
+ border-color: #ddd
+}
+.panel-default>.panel-heading {
+ color: #333;
+ background-color: #f5f5f5;
+ border-color: #ddd
+}
+.panel-default>.panel-heading+.panel-collapse>.panel-body {
+ border-top-color: #ddd
+}
+.panel-default>.panel-heading .badge {
+ color: #f5f5f5;
+ background-color: #333
+}
+.panel-default>.panel-footer+.panel-collapse>.panel-body {
+ border-bottom-color: #ddd
+}
+.panel-primary {
+ border-color: #428bca
+}
+.panel-primary>.panel-heading {
+ color: #fff;
+ background-color: #428bca;
+ border-color: #428bca
+}
+.panel-primary>.panel-heading+.panel-collapse>.panel-body {
+ border-top-color: #428bca
+}
+.panel-primary>.panel-heading .badge {
+ color: #428bca;
+ background-color: #fff
+}
+.panel-primary>.panel-footer+.panel-collapse>.panel-body {
+ border-bottom-color: #428bca
+}
+.panel-success {
+ border-color: #d6e9c6
+}
+.panel-success>.panel-heading {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #d6e9c6
+}
+.panel-success>.panel-heading+.panel-collapse>.panel-body {
+ border-top-color: #d6e9c6
+}
+.panel-success>.panel-heading .badge {
+ color: #dff0d8;
+ background-color: #3c763d
+}
+.panel-success>.panel-footer+.panel-collapse>.panel-body {
+ border-bottom-color: #d6e9c6
+}
+.panel-info {
+ border-color: #bce8f1
+}
+.panel-info>.panel-heading {
+ color: #31708f;
+ background-color: #d9edf7;
+ border-color: #bce8f1
+}
+.panel-info>.panel-heading+.panel-collapse>.panel-body {
+ border-top-color: #bce8f1
+}
+.panel-info>.panel-heading .badge {
+ color: #d9edf7;
+ background-color: #31708f
+}
+.panel-info>.panel-footer+.panel-collapse>.panel-body {
+ border-bottom-color: #bce8f1
+}
+.panel-warning {
+ border-color: #faebcc
+}
+.panel-warning>.panel-heading {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ border-color: #faebcc
+}
+.panel-warning>.panel-heading+.panel-collapse>.panel-body {
+ border-top-color: #faebcc
+}
+.panel-warning>.panel-heading .badge {
+ color: #fcf8e3;
+ background-color: #8a6d3b
+}
+.panel-warning>.panel-footer+.panel-collapse>.panel-body {
+ border-bottom-color: #faebcc
+}
+.panel-danger {
+ border-color: #ebccd1
+}
+.panel-danger>.panel-heading {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #ebccd1
+}
+.panel-danger>.panel-heading+.panel-collapse>.panel-body {
+ border-top-color: #ebccd1
+}
+.panel-danger>.panel-heading .badge {
+ color: #f2dede;
+ background-color: #a94442
+}
+.panel-danger>.panel-footer+.panel-collapse>.panel-body {
+ border-bottom-color: #ebccd1
+}
+.embed-responsive {
+ position: relative;
+ display: block;
+ height: 0;
+ padding: 0
+}
+.embed-responsive .embed-responsive-item,
+.embed-responsive embed,
+.embed-responsive iframe,
+.embed-responsive object {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ height: 100%;
+ width: 100%;
+ border: 0
+}
+.embed-responsive.embed-responsive-16by9 {
+ padding-bottom: 56.25%
+}
+.embed-responsive.embed-responsive-4by3 {
+ padding-bottom: 75%
+}
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border: 1px solid #e3e3e3;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05)
+}
+.well blockquote {
+ border-color: #ddd;
+ border-color: rgba(0, 0, 0, .15)
+}
+.well-lg {
+ padding: 24px;
+ border-radius: 6px
+}
+.well-sm {
+ padding: 9px;
+ border-radius: 3px
+}
+.close {
+ font-size: 21px;
+ line-height: 1;
+ color: #000;
+ text-shadow: 0 1px 0 #fff;
+ opacity: .2;
+ filter: alpha(opacity=20)
+}
+.carousel-caption,
+.carousel-control {
+ text-shadow: 0 1px 2px rgba(0, 0, 0, .6)
+}
+.close:focus,
+.close:hover {
+ color: #000;
+ text-decoration: none;
+ cursor: pointer;
+ opacity: .5;
+ filter: alpha(opacity=50)
+}
+button.close {
+ padding: 0;
+ cursor: pointer;
+ background: 0 0;
+ border: 0;
+ -webkit-appearance: none
+}
+.modal-content,
+.popover {
+ background-clip: padding-box
+}
+.modal {
+ display: none;
+ position: fixed;
+ z-index: 1050;
+ -webkit-overflow-scrolling: touch;
+ outline: 0
+}
+.modal.fade .modal-dialog {
+ -webkit-transform: translate3d(0, -25%, 0);
+ transform: translate3d(0, -25%, 0);
+ -webkit-transition: -webkit-transform .3s ease-out;
+ -moz-transition: -moz-transform .3s ease-out;
+ -o-transition: -o-transform .3s ease-out;
+ transition: transform .3s ease-out
+}
+.modal.in .modal-dialog {
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0)
+}
+.modal-open .modal {
+ overflow-x: hidden;
+ overflow-y: auto
+}
+.modal-dialog {
+ position: relative;
+ width: auto;
+ margin: 10px
+}
+.modal-content {
+ position: relative;
+ background-color: #fff;
+ border: 1px solid #999;
+ border: 1px solid rgba(0, 0, 0, .2);
+ border-radius: 6px;
+ -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
+ box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
+ outline: 0
+}
+.modal-backdrop {
+ position: fixed;
+ z-index: 1040;
+ background-color: #000
+}
+.modal-backdrop.fade {
+ opacity: 0;
+ filter: alpha(opacity=0)
+}
+.modal-backdrop.in {
+ opacity: .5;
+ filter: alpha(opacity=50)
+}
+.modal-header {
+ padding: 15px;
+ border-bottom: 1px solid #e5e5e5;
+ min-height: 16.43px
+}
+.modal-header .close {
+ margin-top: -2px
+}
+.modal-title {
+ margin: 0;
+ line-height: 1.42857143
+}
+.modal-body {
+ position: relative;
+ padding: 15px
+}
+.modal-footer {
+ padding: 15px;
+ text-align: right;
+ border-top: 1px solid #e5e5e5
+}
+.tooltip.top .tooltip-arrow,
+.tooltip.top-left .tooltip-arrow,
+.tooltip.top-right .tooltip-arrow {
+ bottom: 0;
+ border-width: 5px 5px 0;
+ border-top-color: #000
+}
+.modal-footer .btn+.btn {
+ margin-left: 5px;
+ margin-bottom: 0
+}
+.modal-footer .btn-group .btn+.btn {
+ margin-left: -1px
+}
+.modal-footer .btn-block+.btn-block {
+ margin-left: 0
+}
+.modal-scrollbar-measure {
+ position: absolute;
+ top: -9999px;
+ width: 50px;
+ height: 50px;
+ overflow: scroll
+}
+@media (min-width: 768px) {
+ .modal-dialog {
+ width: 600px;
+ margin: 30px auto
+ }
+ .modal-content {
+ -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
+ box-shadow: 0 5px 15px rgba(0, 0, 0, .5)
+ }
+ .modal-sm {
+ width: 300px
+ }
+}
+@media (min-width: 992px) {
+ .modal-lg {
+ width: 900px
+ }
+}
+.tooltip {
+ position: absolute;
+ z-index: 1070;
+ display: block;
+ visibility: visible;
+ font-size: 9pt;
+ line-height: 1.4;
+ opacity: 0;
+ filter: alpha(opacity=0)
+}
+.tooltip.in {
+ opacity: .9;
+ filter: alpha(opacity=90)
+}
+.tooltip.top {
+ margin-top: -3px;
+ padding: 5px 0
+}
+.tooltip.right {
+ margin-left: 3px;
+ padding: 0 5px
+}
+.tooltip.bottom {
+ margin-top: 3px;
+ padding: 5px 0
+}
+.tooltip.left {
+ margin-left: -3px;
+ padding: 0 5px
+}
+.tooltip-inner {
+ max-width: 200px;
+ padding: 3px 8px;
+ color: #fff;
+ text-align: center;
+ text-decoration: none;
+ background-color: #000;
+ border-radius: 4px
+}
+.tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid
+}
+.tooltip.top .tooltip-arrow {
+ left: 50%;
+ margin-left: -5px
+}
+.tooltip.top-left .tooltip-arrow {
+ left: 5px
+}
+.tooltip.top-right .tooltip-arrow {
+ right: 5px
+}
+.tooltip.right .tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -5px;
+ border-width: 5px 5px 5px 0;
+ border-right-color: #000
+}
+.tooltip.left .tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -5px;
+ border-width: 5px 0 5px 5px;
+ border-left-color: #000
+}
+.tooltip.bottom .tooltip-arrow,
+.tooltip.bottom-left .tooltip-arrow,
+.tooltip.bottom-right .tooltip-arrow {
+ border-width: 0 5px 5px;
+ border-bottom-color: #000;
+ top: 0
+}
+.tooltip.bottom .tooltip-arrow {
+ left: 50%;
+ margin-left: -5px
+}
+.tooltip.bottom-left .tooltip-arrow {
+ left: 5px
+}
+.tooltip.bottom-right .tooltip-arrow {
+ right: 5px
+}
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1060;
+ display: none;
+ max-width: 276px;
+ padding: 1px;
+ text-align: left;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, .2);
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+ white-space: normal
+}
+.popover.top {
+ margin-top: -10px
+}
+.popover.right {
+ margin-left: 10px
+}
+.popover.bottom {
+ margin-top: 10px
+}
+.popover.left {
+ margin-left: -10px
+}
+.popover-title {
+ margin: 0;
+ padding: 8px 14px;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 18px;
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ebebeb;
+ border-radius: 5px 5px 0 0
+}
+.popover-content {
+ padding: 9px 14px
+}
+.popover>.arrow,
+.popover>.arrow:after {
+ position: absolute;
+ display: block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid
+}
+.carousel,
+.carousel-inner {
+ position: relative
+}
+.popover>.arrow {
+ border-width: 11px
+}
+.popover>.arrow:after {
+ border-width: 10px
+}
+.popover.top>.arrow {
+ left: 50%;
+ margin-left: -11px;
+ border-bottom-width: 0;
+ border-top-color: #999;
+ border-top-color: rgba(0, 0, 0, .25);
+ bottom: -11px
+}
+.popover.top>.arrow:after {
+ content: " ";
+ bottom: 1px;
+ margin-left: -10px;
+ border-bottom-width: 0;
+ border-top-color: #fff
+}
+.popover.left>.arrow:after,
+.popover.right>.arrow:after {
+ content: " ";
+ bottom: -10px
+}
+.popover.right>.arrow {
+ top: 50%;
+ left: -11px;
+ margin-top: -11px;
+ border-left-width: 0;
+ border-right-color: #999;
+ border-right-color: rgba(0, 0, 0, .25)
+}
+.popover.right>.arrow:after {
+ left: 1px;
+ border-left-width: 0;
+ border-right-color: #fff
+}
+.popover.bottom>.arrow {
+ left: 50%;
+ margin-left: -11px;
+ border-top-width: 0;
+ border-bottom-color: #999;
+ border-bottom-color: rgba(0, 0, 0, .25);
+ top: -11px
+}
+.popover.bottom>.arrow:after {
+ content: " ";
+ top: 1px;
+ margin-left: -10px;
+ border-top-width: 0;
+ border-bottom-color: #fff
+}
+.popover.left>.arrow {
+ top: 50%;
+ right: -11px;
+ margin-top: -11px;
+ border-right-width: 0;
+ border-left-color: #999;
+ border-left-color: rgba(0, 0, 0, .25)
+}
+.popover.left>.arrow:after {
+ right: 1px;
+ border-right-width: 0;
+ border-left-color: #fff
+}
+.carousel-inner {
+ width: 100%
+}
+.carousel-inner>.item {
+ display: none;
+ position: relative;
+ -webkit-transition: .6s ease-in-out left;
+ -o-transition: .6s ease-in-out left;
+ transition: .6s ease-in-out left
+}
+.carousel-inner>.item>a>img,
+.carousel-inner>.item>img {
+ line-height: 1
+}
+.carousel-inner>.active,
+.carousel-inner>.next,
+.carousel-inner>.prev {
+ display: block
+}
+.carousel-inner>.active {
+ left: 0
+}
+.carousel-inner>.next,
+.carousel-inner>.prev {
+ position: absolute;
+ top: 0;
+ width: 100%
+}
+.carousel-inner>.next {
+ left: 100%
+}
+.carousel-inner>.prev {
+ left: -100%
+}
+.carousel-inner>.next.left,
+.carousel-inner>.prev.right {
+ left: 0
+}
+.carousel-inner>.active.left {
+ left: -100%
+}
+.carousel-inner>.active.right {
+ left: 100%
+}
+.carousel-control {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ width: 15%;
+ opacity: .5;
+ filter: alpha(opacity=50);
+ font-size: 20px;
+ color: #fff;
+ text-align: center
+}
+.carousel-control.left {
+ background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .0001) 100%);
+ background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .0001) 100%);
+ background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .0001) 100%);
+ background-repeat: repeat-x;
+ filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)
+}
+.carousel-control.right {
+ left: auto;
+ right: 0;
+ background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0, rgba(0, 0, 0, .5) 100%);
+ background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0, rgba(0, 0, 0, .5) 100%);
+ background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0, rgba(0, 0, 0, .5) 100%);
+ background-repeat: repeat-x;
+ filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)
+}
+.carousel-control:focus,
+.carousel-control:hover {
+ outline: 0;
+ color: #fff;
+ text-decoration: none;
+ opacity: .9;
+ filter: alpha(opacity=90)
+}
+.carousel-control .glyphicon-chevron-left,
+.carousel-control .glyphicon-chevron-right,
+.carousel-control .icon-next,
+.carousel-control .icon-prev {
+ position: absolute;
+ top: 50%;
+ z-index: 5;
+ display: inline-block
+}
+.carousel-control .glyphicon-chevron-left,
+.carousel-control .icon-prev {
+ left: 50%;
+ margin-left: -10px
+}
+.carousel-control .glyphicon-chevron-right,
+.carousel-control .icon-next {
+ right: 50%;
+ margin-right: -10px
+}
+.carousel-control .icon-next,
+.carousel-control .icon-prev {
+ width: 20px;
+ height: 20px;
+ margin-top: -10px;
+ font-family: serif
+}
+.carousel-control .icon-prev:before {
+ content: '\2039'
+}
+.carousel-control .icon-next:before {
+ content: '\203a'
+}
+.carousel-indicators {
+ position: absolute;
+ bottom: 10px;
+ left: 50%;
+ z-index: 15;
+ width: 60%;
+ margin-left: -30%;
+ padding-left: 0;
+ list-style: none;
+ text-align: center
+}
+.carousel-indicators li {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ margin: 1px;
+ text-indent: -999px;
+ border: 1px solid #fff;
+ border-radius: 10px;
+ cursor: pointer;
+ background-color: #000\9;
+ background-color: transparent
+}
+.carousel-indicators .active {
+ margin: 0;
+ width: 9pt;
+ height: 9pt;
+ background-color: #fff
+}
+.carousel-caption {
+ position: absolute;
+ left: 15%;
+ right: 15%;
+ bottom: 20px;
+ z-index: 10;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ color: #fff;
+ text-align: center
+}
+.carousel-caption .btn,
+.text-hide {
+ text-shadow: none
+}
+@media screen and (min-width: 768px) {
+ .carousel-control .glyphicon-chevron-left,
+ .carousel-control .glyphicon-chevron-right,
+ .carousel-control .icon-next,
+ .carousel-control .icon-prev {
+ width: 30px;
+ height: 30px;
+ margin-top: -15px;
+ font-size: 30px
+ }
+ .carousel-control .glyphicon-chevron-left,
+ .carousel-control .icon-prev {
+ margin-left: -15px
+ }
+ .carousel-control .glyphicon-chevron-right,
+ .carousel-control .icon-next {
+ margin-right: -15px
+ }
+ .carousel-caption {
+ left: 20%;
+ right: 20%;
+ padding-bottom: 30px
+ }
+ .carousel-indicators {
+ bottom: 20px
+ }
+}
+.btn-group-vertical>.btn-group:after,
+.btn-group-vertical>.btn-group:before,
+.btn-toolbar:after,
+.btn-toolbar:before,
+.clearfix:after,
+.clearfix:before,
+.container-fluid:after,
+.container-fluid:before,
+.container:after,
+.container:before,
+.dl-horizontal dd:after,
+.dl-horizontal dd:before,
+.form-horizontal .form-group:after,
+.form-horizontal .form-group:before,
+.modal-footer:after,
+.modal-footer:before,
+.nav:after,
+.nav:before,
+.navbar-collapse:after,
+.navbar-collapse:before,
+.navbar-header:after,
+.navbar-header:before,
+.navbar:after,
+.navbar:before,
+.pager:after,
+.pager:before,
+.panel-body:after,
+.panel-body:before,
+.row:after,
+.row:before {
+ content: " ";
+ display: table
+}
+.btn-group-vertical>.btn-group:after,
+.btn-toolbar:after,
+.clearfix:after,
+.container-fluid:after,
+.container:after,
+.dl-horizontal dd:after,
+.form-horizontal .form-group:after,
+.modal-footer:after,
+.nav:after,
+.navbar-collapse:after,
+.navbar-header:after,
+.navbar:after,
+.pager:after,
+.panel-body:after,
+.row:after {
+ clear: both
+}
+.center-block {
+ display: block;
+ margin-left: auto;
+ margin-right: auto
+}
+.pull-right {
+ float: right!important
+}
+.pull-left {
+ float: left!important
+}
+.hide {
+ display: none!important
+}
+.show {
+ display: block!important
+}
+.hidden,
+.visible-lg,
+.visible-lg-block,
+.visible-lg-inline,
+.visible-lg-inline-block,
+.visible-md,
+.visible-md-block,
+.visible-md-inline,
+.visible-md-inline-block,
+.visible-sm,
+.visible-sm-block,
+.visible-sm-inline,
+.visible-sm-inline-block,
+.visible-xs,
+.visible-xs-block,
+.visible-xs-inline,
+.visible-xs-inline-block {
+ display: none!important
+}
+.invisible {
+ visibility: hidden
+}
+.text-hide {
+ font: 0/0 a;
+ color: transparent;
+ background-color: transparent;
+ border: 0
+}
+.hidden {
+ visibility: hidden!important
+}
+.affix {
+ position: fixed;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0)
+}
+@-ms-viewport {
+ width: device-width
+}
+@media (max-width: 767px) {
+ .visible-xs {
+ display: block!important
+ }
+ table.visible-xs {
+ display: table
+ }
+ tr.visible-xs {
+ display: table-row!important
+ }
+ td.visible-xs,
+ th.visible-xs {
+ display: table-cell!important
+ }
+ .visible-xs-block {
+ display: block!important
+ }
+ .visible-xs-inline {
+ display: inline!important
+ }
+ .visible-xs-inline-block {
+ display: inline-block!important
+ }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-sm {
+ display: block!important
+ }
+ table.visible-sm {
+ display: table
+ }
+ tr.visible-sm {
+ display: table-row!important
+ }
+ td.visible-sm,
+ th.visible-sm {
+ display: table-cell!important
+ }
+ .visible-sm-block {
+ display: block!important
+ }
+ .visible-sm-inline {
+ display: inline!important
+ }
+ .visible-sm-inline-block {
+ display: inline-block!important
+ }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-md {
+ display: block!important
+ }
+ table.visible-md {
+ display: table
+ }
+ tr.visible-md {
+ display: table-row!important
+ }
+ td.visible-md,
+ th.visible-md {
+ display: table-cell!important
+ }
+ .visible-md-block {
+ display: block!important
+ }
+ .visible-md-inline {
+ display: inline!important
+ }
+ .visible-md-inline-block {
+ display: inline-block!important
+ }
+}
+@media (min-width: 1200px) {
+ .visible-lg {
+ display: block!important
+ }
+ table.visible-lg {
+ display: table
+ }
+ tr.visible-lg {
+ display: table-row!important
+ }
+ td.visible-lg,
+ th.visible-lg {
+ display: table-cell!important
+ }
+ .visible-lg-block {
+ display: block!important
+ }
+ .visible-lg-inline {
+ display: inline!important
+ }
+ .visible-lg-inline-block {
+ display: inline-block!important
+ }
+ .hidden-lg {
+ display: none!important
+ }
+}
+@media (max-width: 767px) {
+ .hidden-xs {
+ display: none!important
+ }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+ .hidden-sm {
+ display: none!important
+ }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+ .hidden-md {
+ display: none!important
+ }
+}
+.visible-print {
+ display: none!important
+}
+@media print {
+ .visible-print {
+ display: block!important
+ }
+ table.visible-print {
+ display: table
+ }
+ tr.visible-print {
+ display: table-row!important
+ }
+ td.visible-print,
+ th.visible-print {
+ display: table-cell!important
+ }
+}
+.visible-print-block {
+ display: none!important
+}
+@media print {
+ .visible-print-block {
+ display: block!important
+ }
+}
+.visible-print-inline {
+ display: none!important
+}
+@media print {
+ .visible-print-inline {
+ display: inline!important
+ }
+}
+.visible-print-inline-block {
+ display: none!important
+}
+@media print {
+ .visible-print-inline-block {
+ display: inline-block!important
+ }
+ .hidden-print {
+ display: none!important
+ }
+} \ No newline at end of file
diff --git a/tools/infra-dashboard/css/dataTables.bootstrap.min.css b/tools/infra-dashboard/css/dataTables.bootstrap.min.css
new file mode 100644
index 00000000..745f2996
--- /dev/null
+++ b/tools/infra-dashboard/css/dataTables.bootstrap.min.css
@@ -0,0 +1 @@
+table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:8px;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:8px;right:8px;display:block;font-family:'Glyphicons Halflings';opacity:0.5}table.dataTable thead .sorting:after{opacity:0.2;content:"\e150"}table.dataTable thead .sorting_asc:after{content:"\e155"}table.dataTable thead .sorting_desc:after{content:"\e156"}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-condensed>thead>tr>th{padding-right:20px}table.dataTable.table-condensed .sorting:after,table.dataTable.table-condensed .sorting_asc:after,table.dataTable.table-condensed .sorting_desc:after{top:6px;right:6px}table.table-bordered.dataTable{border-collapse:separate !important}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0}
diff --git a/tools/infra-dashboard/css/font-awesome.css b/tools/infra-dashboard/css/font-awesome.css
new file mode 100644
index 00000000..2dcdc220
--- /dev/null
+++ b/tools/infra-dashboard/css/font-awesome.css
@@ -0,0 +1,1801 @@
+/*!
+ * Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome
+ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */
+/* FONT PATH
+ * -------------------------- */
+@font-face {
+ font-family: 'FontAwesome';
+ src: url('../fonts/fontawesome-webfont.eot?v=4.3.0');
+ src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+.fa {
+ display: inline-block;
+ font: normal normal normal 14px/1 FontAwesome;
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ transform: translate(0, 0);
+}
+/* makes the font 33% larger relative to the icon container */
+.fa-lg {
+ font-size: 1.33333333em;
+ line-height: 0.75em;
+ vertical-align: -15%;
+}
+.fa-2x {
+ font-size: 2em;
+}
+.fa-3x {
+ font-size: 3em;
+}
+.fa-4x {
+ font-size: 4em;
+}
+.fa-5x {
+ font-size: 5em;
+}
+.fa-fw {
+ width: 1.28571429em;
+ text-align: center;
+}
+.fa-ul {
+ padding-left: 0;
+ margin-left: 2.14285714em;
+ list-style-type: none;
+}
+.fa-ul > li {
+ position: relative;
+}
+.fa-li {
+ position: absolute;
+ left: -2.14285714em;
+ width: 2.14285714em;
+ top: 0.14285714em;
+ text-align: center;
+}
+.fa-li.fa-lg {
+ left: -1.85714286em;
+}
+.fa-border {
+ padding: .2em .25em .15em;
+ border: solid 0.08em #eeeeee;
+ border-radius: .1em;
+}
+.pull-right {
+ float: right;
+}
+.pull-left {
+ float: left;
+}
+.fa.pull-left {
+ margin-right: .3em;
+}
+.fa.pull-right {
+ margin-left: .3em;
+}
+.fa-spin {
+ -webkit-animation: fa-spin 2s infinite linear;
+ animation: fa-spin 2s infinite linear;
+}
+.fa-pulse {
+ -webkit-animation: fa-spin 1s infinite steps(8);
+ animation: fa-spin 1s infinite steps(8);
+}
+@-webkit-keyframes fa-spin {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+@keyframes fa-spin {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+.fa-rotate-90 {
+ filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
+ -webkit-transform: rotate(90deg);
+ -ms-transform: rotate(90deg);
+ transform: rotate(90deg);
+}
+.fa-rotate-180 {
+ filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
+ -webkit-transform: rotate(180deg);
+ -ms-transform: rotate(180deg);
+ transform: rotate(180deg);
+}
+.fa-rotate-270 {
+ filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
+ -webkit-transform: rotate(270deg);
+ -ms-transform: rotate(270deg);
+ transform: rotate(270deg);
+}
+.fa-flip-horizontal {
+ filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);
+ -webkit-transform: scale(-1, 1);
+ -ms-transform: scale(-1, 1);
+ transform: scale(-1, 1);
+}
+.fa-flip-vertical {
+ filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);
+ -webkit-transform: scale(1, -1);
+ -ms-transform: scale(1, -1);
+ transform: scale(1, -1);
+}
+:root .fa-rotate-90,
+:root .fa-rotate-180,
+:root .fa-rotate-270,
+:root .fa-flip-horizontal,
+:root .fa-flip-vertical {
+ filter: none;
+}
+.fa-stack {
+ position: relative;
+ display: inline-block;
+ width: 2em;
+ height: 2em;
+ line-height: 2em;
+ vertical-align: middle;
+}
+.fa-stack-1x,
+.fa-stack-2x {
+ position: absolute;
+ left: 0;
+ width: 100%;
+ text-align: center;
+}
+.fa-stack-1x {
+ line-height: inherit;
+}
+.fa-stack-2x {
+ font-size: 2em;
+}
+.fa-inverse {
+ color: #ffffff;
+}
+/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
+ readers do not read off random characters that represent icons */
+.fa-glass:before {
+ content: "\f000";
+}
+.fa-music:before {
+ content: "\f001";
+}
+.fa-search:before {
+ content: "\f002";
+}
+.fa-envelope-o:before {
+ content: "\f003";
+}
+.fa-heart:before {
+ content: "\f004";
+}
+.fa-star:before {
+ content: "\f005";
+}
+.fa-star-o:before {
+ content: "\f006";
+}
+.fa-user:before {
+ content: "\f007";
+}
+.fa-film:before {
+ content: "\f008";
+}
+.fa-th-large:before {
+ content: "\f009";
+}
+.fa-th:before {
+ content: "\f00a";
+}
+.fa-th-list:before {
+ content: "\f00b";
+}
+.fa-check:before {
+ content: "\f00c";
+}
+.fa-remove:before,
+.fa-close:before,
+.fa-times:before {
+ content: "\f00d";
+}
+.fa-search-plus:before {
+ content: "\f00e";
+}
+.fa-search-minus:before {
+ content: "\f010";
+}
+.fa-power-off:before {
+ content: "\f011";
+}
+.fa-signal:before {
+ content: "\f012";
+}
+.fa-gear:before,
+.fa-cog:before {
+ content: "\f013";
+}
+.fa-trash-o:before {
+ content: "\f014";
+}
+.fa-home:before {
+ content: "\f015";
+}
+.fa-file-o:before {
+ content: "\f016";
+}
+.fa-clock-o:before {
+ content: "\f017";
+}
+.fa-road:before {
+ content: "\f018";
+}
+.fa-download:before {
+ content: "\f019";
+}
+.fa-arrow-circle-o-down:before {
+ content: "\f01a";
+}
+.fa-arrow-circle-o-up:before {
+ content: "\f01b";
+}
+.fa-inbox:before {
+ content: "\f01c";
+}
+.fa-play-circle-o:before {
+ content: "\f01d";
+}
+.fa-rotate-right:before,
+.fa-repeat:before {
+ content: "\f01e";
+}
+.fa-refresh:before {
+ content: "\f021";
+}
+.fa-list-alt:before {
+ content: "\f022";
+}
+.fa-lock:before {
+ content: "\f023";
+}
+.fa-flag:before {
+ content: "\f024";
+}
+.fa-headphones:before {
+ content: "\f025";
+}
+.fa-volume-off:before {
+ content: "\f026";
+}
+.fa-volume-down:before {
+ content: "\f027";
+}
+.fa-volume-up:before {
+ content: "\f028";
+}
+.fa-qrcode:before {
+ content: "\f029";
+}
+.fa-barcode:before {
+ content: "\f02a";
+}
+.fa-tag:before {
+ content: "\f02b";
+}
+.fa-tags:before {
+ content: "\f02c";
+}
+.fa-book:before {
+ content: "\f02d";
+}
+.fa-bookmark:before {
+ content: "\f02e";
+}
+.fa-print:before {
+ content: "\f02f";
+}
+.fa-camera:before {
+ content: "\f030";
+}
+.fa-font:before {
+ content: "\f031";
+}
+.fa-bold:before {
+ content: "\f032";
+}
+.fa-italic:before {
+ content: "\f033";
+}
+.fa-text-height:before {
+ content: "\f034";
+}
+.fa-text-width:before {
+ content: "\f035";
+}
+.fa-align-left:before {
+ content: "\f036";
+}
+.fa-align-center:before {
+ content: "\f037";
+}
+.fa-align-right:before {
+ content: "\f038";
+}
+.fa-align-justify:before {
+ content: "\f039";
+}
+.fa-list:before {
+ content: "\f03a";
+}
+.fa-dedent:before,
+.fa-outdent:before {
+ content: "\f03b";
+}
+.fa-indent:before {
+ content: "\f03c";
+}
+.fa-video-camera:before {
+ content: "\f03d";
+}
+.fa-photo:before,
+.fa-image:before,
+.fa-picture-o:before {
+ content: "\f03e";
+}
+.fa-pencil:before {
+ content: "\f040";
+}
+.fa-map-marker:before {
+ content: "\f041";
+}
+.fa-adjust:before {
+ content: "\f042";
+}
+.fa-tint:before {
+ content: "\f043";
+}
+.fa-edit:before,
+.fa-pencil-square-o:before {
+ content: "\f044";
+}
+.fa-share-square-o:before {
+ content: "\f045";
+}
+.fa-check-square-o:before {
+ content: "\f046";
+}
+.fa-arrows:before {
+ content: "\f047";
+}
+.fa-step-backward:before {
+ content: "\f048";
+}
+.fa-fast-backward:before {
+ content: "\f049";
+}
+.fa-backward:before {
+ content: "\f04a";
+}
+.fa-play:before {
+ content: "\f04b";
+}
+.fa-pause:before {
+ content: "\f04c";
+}
+.fa-stop:before {
+ content: "\f04d";
+}
+.fa-forward:before {
+ content: "\f04e";
+}
+.fa-fast-forward:before {
+ content: "\f050";
+}
+.fa-step-forward:before {
+ content: "\f051";
+}
+.fa-eject:before {
+ content: "\f052";
+}
+.fa-chevron-left:before {
+ content: "\f053";
+}
+.fa-chevron-right:before {
+ content: "\f054";
+}
+.fa-plus-circle:before {
+ content: "\f055";
+}
+.fa-minus-circle:before {
+ content: "\f056";
+}
+.fa-times-circle:before {
+ content: "\f057";
+}
+.fa-check-circle:before {
+ content: "\f058";
+}
+.fa-question-circle:before {
+ content: "\f059";
+}
+.fa-info-circle:before {
+ content: "\f05a";
+}
+.fa-crosshairs:before {
+ content: "\f05b";
+}
+.fa-times-circle-o:before {
+ content: "\f05c";
+}
+.fa-check-circle-o:before {
+ content: "\f05d";
+}
+.fa-ban:before {
+ content: "\f05e";
+}
+.fa-arrow-left:before {
+ content: "\f060";
+}
+.fa-arrow-right:before {
+ content: "\f061";
+}
+.fa-arrow-up:before {
+ content: "\f062";
+}
+.fa-arrow-down:before {
+ content: "\f063";
+}
+.fa-mail-forward:before,
+.fa-share:before {
+ content: "\f064";
+}
+.fa-expand:before {
+ content: "\f065";
+}
+.fa-compress:before {
+ content: "\f066";
+}
+.fa-plus:before {
+ content: "\f067";
+}
+.fa-minus:before {
+ content: "\f068";
+}
+.fa-asterisk:before {
+ content: "\f069";
+}
+.fa-exclamation-circle:before {
+ content: "\f06a";
+}
+.fa-gift:before {
+ content: "\f06b";
+}
+.fa-leaf:before {
+ content: "\f06c";
+}
+.fa-fire:before {
+ content: "\f06d";
+}
+.fa-eye:before {
+ content: "\f06e";
+}
+.fa-eye-slash:before {
+ content: "\f070";
+}
+.fa-warning:before,
+.fa-exclamation-triangle:before {
+ content: "\f071";
+}
+.fa-plane:before {
+ content: "\f072";
+}
+.fa-calendar:before {
+ content: "\f073";
+}
+.fa-random:before {
+ content: "\f074";
+}
+.fa-comment:before {
+ content: "\f075";
+}
+.fa-magnet:before {
+ content: "\f076";
+}
+.fa-chevron-up:before {
+ content: "\f077";
+}
+.fa-chevron-down:before {
+ content: "\f078";
+}
+.fa-retweet:before {
+ content: "\f079";
+}
+.fa-shopping-cart:before {
+ content: "\f07a";
+}
+.fa-folder:before {
+ content: "\f07b";
+}
+.fa-folder-open:before {
+ content: "\f07c";
+}
+.fa-arrows-v:before {
+ content: "\f07d";
+}
+.fa-arrows-h:before {
+ content: "\f07e";
+}
+.fa-bar-chart-o:before,
+.fa-bar-chart:before {
+ content: "\f080";
+}
+.fa-twitter-square:before {
+ content: "\f081";
+}
+.fa-facebook-square:before {
+ content: "\f082";
+}
+.fa-camera-retro:before {
+ content: "\f083";
+}
+.fa-key:before {
+ content: "\f084";
+}
+.fa-gears:before,
+.fa-cogs:before {
+ content: "\f085";
+}
+.fa-comments:before {
+ content: "\f086";
+}
+.fa-thumbs-o-up:before {
+ content: "\f087";
+}
+.fa-thumbs-o-down:before {
+ content: "\f088";
+}
+.fa-star-half:before {
+ content: "\f089";
+}
+.fa-heart-o:before {
+ content: "\f08a";
+}
+.fa-sign-out:before {
+ content: "\f08b";
+}
+.fa-linkedin-square:before {
+ content: "\f08c";
+}
+.fa-thumb-tack:before {
+ content: "\f08d";
+}
+.fa-external-link:before {
+ content: "\f08e";
+}
+.fa-sign-in:before {
+ content: "\f090";
+}
+.fa-trophy:before {
+ content: "\f091";
+}
+.fa-github-square:before {
+ content: "\f092";
+}
+.fa-upload:before {
+ content: "\f093";
+}
+.fa-lemon-o:before {
+ content: "\f094";
+}
+.fa-phone:before {
+ content: "\f095";
+}
+.fa-square-o:before {
+ content: "\f096";
+}
+.fa-bookmark-o:before {
+ content: "\f097";
+}
+.fa-phone-square:before {
+ content: "\f098";
+}
+.fa-twitter:before {
+ content: "\f099";
+}
+.fa-facebook-f:before,
+.fa-facebook:before {
+ content: "\f09a";
+}
+.fa-github:before {
+ content: "\f09b";
+}
+.fa-unlock:before {
+ content: "\f09c";
+}
+.fa-credit-card:before {
+ content: "\f09d";
+}
+.fa-rss:before {
+ content: "\f09e";
+}
+.fa-hdd-o:before {
+ content: "\f0a0";
+}
+.fa-bullhorn:before {
+ content: "\f0a1";
+}
+.fa-bell:before {
+ content: "\f0f3";
+}
+.fa-certificate:before {
+ content: "\f0a3";
+}
+.fa-hand-o-right:before {
+ content: "\f0a4";
+}
+.fa-hand-o-left:before {
+ content: "\f0a5";
+}
+.fa-hand-o-up:before {
+ content: "\f0a6";
+}
+.fa-hand-o-down:before {
+ content: "\f0a7";
+}
+.fa-arrow-circle-left:before {
+ content: "\f0a8";
+}
+.fa-arrow-circle-right:before {
+ content: "\f0a9";
+}
+.fa-arrow-circle-up:before {
+ content: "\f0aa";
+}
+.fa-arrow-circle-down:before {
+ content: "\f0ab";
+}
+.fa-globe:before {
+ content: "\f0ac";
+}
+.fa-wrench:before {
+ content: "\f0ad";
+}
+.fa-tasks:before {
+ content: "\f0ae";
+}
+.fa-filter:before {
+ content: "\f0b0";
+}
+.fa-briefcase:before {
+ content: "\f0b1";
+}
+.fa-arrows-alt:before {
+ content: "\f0b2";
+}
+.fa-group:before,
+.fa-users:before {
+ content: "\f0c0";
+}
+.fa-chain:before,
+.fa-link:before {
+ content: "\f0c1";
+}
+.fa-cloud:before {
+ content: "\f0c2";
+}
+.fa-flask:before {
+ content: "\f0c3";
+}
+.fa-cut:before,
+.fa-scissors:before {
+ content: "\f0c4";
+}
+.fa-copy:before,
+.fa-files-o:before {
+ content: "\f0c5";
+}
+.fa-paperclip:before {
+ content: "\f0c6";
+}
+.fa-save:before,
+.fa-floppy-o:before {
+ content: "\f0c7";
+}
+.fa-square:before {
+ content: "\f0c8";
+}
+.fa-navicon:before,
+.fa-reorder:before,
+.fa-bars:before {
+ content: "\f0c9";
+}
+.fa-list-ul:before {
+ content: "\f0ca";
+}
+.fa-list-ol:before {
+ content: "\f0cb";
+}
+.fa-strikethrough:before {
+ content: "\f0cc";
+}
+.fa-underline:before {
+ content: "\f0cd";
+}
+.fa-table:before {
+ content: "\f0ce";
+}
+.fa-magic:before {
+ content: "\f0d0";
+}
+.fa-truck:before {
+ content: "\f0d1";
+}
+.fa-pinterest:before {
+ content: "\f0d2";
+}
+.fa-pinterest-square:before {
+ content: "\f0d3";
+}
+.fa-google-plus-square:before {
+ content: "\f0d4";
+}
+.fa-google-plus:before {
+ content: "\f0d5";
+}
+.fa-money:before {
+ content: "\f0d6";
+}
+.fa-caret-down:before {
+ content: "\f0d7";
+}
+.fa-caret-up:before {
+ content: "\f0d8";
+}
+.fa-caret-left:before {
+ content: "\f0d9";
+}
+.fa-caret-right:before {
+ content: "\f0da";
+}
+.fa-columns:before {
+ content: "\f0db";
+}
+.fa-unsorted:before,
+.fa-sort:before {
+ content: "\f0dc";
+}
+.fa-sort-down:before,
+.fa-sort-desc:before {
+ content: "\f0dd";
+}
+.fa-sort-up:before,
+.fa-sort-asc:before {
+ content: "\f0de";
+}
+.fa-envelope:before {
+ content: "\f0e0";
+}
+.fa-linkedin:before {
+ content: "\f0e1";
+}
+.fa-rotate-left:before,
+.fa-undo:before {
+ content: "\f0e2";
+}
+.fa-legal:before,
+.fa-gavel:before {
+ content: "\f0e3";
+}
+.fa-dashboard:before,
+.fa-tachometer:before {
+ content: "\f0e4";
+}
+.fa-comment-o:before {
+ content: "\f0e5";
+}
+.fa-comments-o:before {
+ content: "\f0e6";
+}
+.fa-flash:before,
+.fa-bolt:before {
+ content: "\f0e7";
+}
+.fa-sitemap:before {
+ content: "\f0e8";
+}
+.fa-umbrella:before {
+ content: "\f0e9";
+}
+.fa-paste:before,
+.fa-clipboard:before {
+ content: "\f0ea";
+}
+.fa-lightbulb-o:before {
+ content: "\f0eb";
+}
+.fa-exchange:before {
+ content: "\f0ec";
+}
+.fa-cloud-download:before {
+ content: "\f0ed";
+}
+.fa-cloud-upload:before {
+ content: "\f0ee";
+}
+.fa-user-md:before {
+ content: "\f0f0";
+}
+.fa-stethoscope:before {
+ content: "\f0f1";
+}
+.fa-suitcase:before {
+ content: "\f0f2";
+}
+.fa-bell-o:before {
+ content: "\f0a2";
+}
+.fa-coffee:before {
+ content: "\f0f4";
+}
+.fa-cutlery:before {
+ content: "\f0f5";
+}
+.fa-file-text-o:before {
+ content: "\f0f6";
+}
+.fa-building-o:before {
+ content: "\f0f7";
+}
+.fa-hospital-o:before {
+ content: "\f0f8";
+}
+.fa-ambulance:before {
+ content: "\f0f9";
+}
+.fa-medkit:before {
+ content: "\f0fa";
+}
+.fa-fighter-jet:before {
+ content: "\f0fb";
+}
+.fa-beer:before {
+ content: "\f0fc";
+}
+.fa-h-square:before {
+ content: "\f0fd";
+}
+.fa-plus-square:before {
+ content: "\f0fe";
+}
+.fa-angle-double-left:before {
+ content: "\f100";
+}
+.fa-angle-double-right:before {
+ content: "\f101";
+}
+.fa-angle-double-up:before {
+ content: "\f102";
+}
+.fa-angle-double-down:before {
+ content: "\f103";
+}
+.fa-angle-left:before {
+ content: "\f104";
+}
+.fa-angle-right:before {
+ content: "\f105";
+}
+.fa-angle-up:before {
+ content: "\f106";
+}
+.fa-angle-down:before {
+ content: "\f107";
+}
+.fa-desktop:before {
+ content: "\f108";
+}
+.fa-laptop:before {
+ content: "\f109";
+}
+.fa-tablet:before {
+ content: "\f10a";
+}
+.fa-mobile-phone:before,
+.fa-mobile:before {
+ content: "\f10b";
+}
+.fa-circle-o:before {
+ content: "\f10c";
+}
+.fa-quote-left:before {
+ content: "\f10d";
+}
+.fa-quote-right:before {
+ content: "\f10e";
+}
+.fa-spinner:before {
+ content: "\f110";
+}
+.fa-circle:before {
+ content: "\f111";
+}
+.fa-mail-reply:before,
+.fa-reply:before {
+ content: "\f112";
+}
+.fa-github-alt:before {
+ content: "\f113";
+}
+.fa-folder-o:before {
+ content: "\f114";
+}
+.fa-folder-open-o:before {
+ content: "\f115";
+}
+.fa-smile-o:before {
+ content: "\f118";
+}
+.fa-frown-o:before {
+ content: "\f119";
+}
+.fa-meh-o:before {
+ content: "\f11a";
+}
+.fa-gamepad:before {
+ content: "\f11b";
+}
+.fa-keyboard-o:before {
+ content: "\f11c";
+}
+.fa-flag-o:before {
+ content: "\f11d";
+}
+.fa-flag-checkered:before {
+ content: "\f11e";
+}
+.fa-terminal:before {
+ content: "\f120";
+}
+.fa-code:before {
+ content: "\f121";
+}
+.fa-mail-reply-all:before,
+.fa-reply-all:before {
+ content: "\f122";
+}
+.fa-star-half-empty:before,
+.fa-star-half-full:before,
+.fa-star-half-o:before {
+ content: "\f123";
+}
+.fa-location-arrow:before {
+ content: "\f124";
+}
+.fa-crop:before {
+ content: "\f125";
+}
+.fa-code-fork:before {
+ content: "\f126";
+}
+.fa-unlink:before,
+.fa-chain-broken:before {
+ content: "\f127";
+}
+.fa-question:before {
+ content: "\f128";
+}
+.fa-info:before {
+ content: "\f129";
+}
+.fa-exclamation:before {
+ content: "\f12a";
+}
+.fa-superscript:before {
+ content: "\f12b";
+}
+.fa-subscript:before {
+ content: "\f12c";
+}
+.fa-eraser:before {
+ content: "\f12d";
+}
+.fa-puzzle-piece:before {
+ content: "\f12e";
+}
+.fa-microphone:before {
+ content: "\f130";
+}
+.fa-microphone-slash:before {
+ content: "\f131";
+}
+.fa-shield:before {
+ content: "\f132";
+}
+.fa-calendar-o:before {
+ content: "\f133";
+}
+.fa-fire-extinguisher:before {
+ content: "\f134";
+}
+.fa-rocket:before {
+ content: "\f135";
+}
+.fa-maxcdn:before {
+ content: "\f136";
+}
+.fa-chevron-circle-left:before {
+ content: "\f137";
+}
+.fa-chevron-circle-right:before {
+ content: "\f138";
+}
+.fa-chevron-circle-up:before {
+ content: "\f139";
+}
+.fa-chevron-circle-down:before {
+ content: "\f13a";
+}
+.fa-html5:before {
+ content: "\f13b";
+}
+.fa-css3:before {
+ content: "\f13c";
+}
+.fa-anchor:before {
+ content: "\f13d";
+}
+.fa-unlock-alt:before {
+ content: "\f13e";
+}
+.fa-bullseye:before {
+ content: "\f140";
+}
+.fa-ellipsis-h:before {
+ content: "\f141";
+}
+.fa-ellipsis-v:before {
+ content: "\f142";
+}
+.fa-rss-square:before {
+ content: "\f143";
+}
+.fa-play-circle:before {
+ content: "\f144";
+}
+.fa-ticket:before {
+ content: "\f145";
+}
+.fa-minus-square:before {
+ content: "\f146";
+}
+.fa-minus-square-o:before {
+ content: "\f147";
+}
+.fa-level-up:before {
+ content: "\f148";
+}
+.fa-level-down:before {
+ content: "\f149";
+}
+.fa-check-square:before {
+ content: "\f14a";
+}
+.fa-pencil-square:before {
+ content: "\f14b";
+}
+.fa-external-link-square:before {
+ content: "\f14c";
+}
+.fa-share-square:before {
+ content: "\f14d";
+}
+.fa-compass:before {
+ content: "\f14e";
+}
+.fa-toggle-down:before,
+.fa-caret-square-o-down:before {
+ content: "\f150";
+}
+.fa-toggle-up:before,
+.fa-caret-square-o-up:before {
+ content: "\f151";
+}
+.fa-toggle-right:before,
+.fa-caret-square-o-right:before {
+ content: "\f152";
+}
+.fa-euro:before,
+.fa-eur:before {
+ content: "\f153";
+}
+.fa-gbp:before {
+ content: "\f154";
+}
+.fa-dollar:before,
+.fa-usd:before {
+ content: "\f155";
+}
+.fa-rupee:before,
+.fa-inr:before {
+ content: "\f156";
+}
+.fa-cny:before,
+.fa-rmb:before,
+.fa-yen:before,
+.fa-jpy:before {
+ content: "\f157";
+}
+.fa-ruble:before,
+.fa-rouble:before,
+.fa-rub:before {
+ content: "\f158";
+}
+.fa-won:before,
+.fa-krw:before {
+ content: "\f159";
+}
+.fa-bitcoin:before,
+.fa-btc:before {
+ content: "\f15a";
+}
+.fa-file:before {
+ content: "\f15b";
+}
+.fa-file-text:before {
+ content: "\f15c";
+}
+.fa-sort-alpha-asc:before {
+ content: "\f15d";
+}
+.fa-sort-alpha-desc:before {
+ content: "\f15e";
+}
+.fa-sort-amount-asc:before {
+ content: "\f160";
+}
+.fa-sort-amount-desc:before {
+ content: "\f161";
+}
+.fa-sort-numeric-asc:before {
+ content: "\f162";
+}
+.fa-sort-numeric-desc:before {
+ content: "\f163";
+}
+.fa-thumbs-up:before {
+ content: "\f164";
+}
+.fa-thumbs-down:before {
+ content: "\f165";
+}
+.fa-youtube-square:before {
+ content: "\f166";
+}
+.fa-youtube:before {
+ content: "\f167";
+}
+.fa-xing:before {
+ content: "\f168";
+}
+.fa-xing-square:before {
+ content: "\f169";
+}
+.fa-youtube-play:before {
+ content: "\f16a";
+}
+.fa-dropbox:before {
+ content: "\f16b";
+}
+.fa-stack-overflow:before {
+ content: "\f16c";
+}
+.fa-instagram:before {
+ content: "\f16d";
+}
+.fa-flickr:before {
+ content: "\f16e";
+}
+.fa-adn:before {
+ content: "\f170";
+}
+.fa-bitbucket:before {
+ content: "\f171";
+}
+.fa-bitbucket-square:before {
+ content: "\f172";
+}
+.fa-tumblr:before {
+ content: "\f173";
+}
+.fa-tumblr-square:before {
+ content: "\f174";
+}
+.fa-long-arrow-down:before {
+ content: "\f175";
+}
+.fa-long-arrow-up:before {
+ content: "\f176";
+}
+.fa-long-arrow-left:before {
+ content: "\f177";
+}
+.fa-long-arrow-right:before {
+ content: "\f178";
+}
+.fa-apple:before {
+ content: "\f179";
+}
+.fa-windows:before {
+ content: "\f17a";
+}
+.fa-android:before {
+ content: "\f17b";
+}
+.fa-linux:before {
+ content: "\f17c";
+}
+.fa-dribbble:before {
+ content: "\f17d";
+}
+.fa-skype:before {
+ content: "\f17e";
+}
+.fa-foursquare:before {
+ content: "\f180";
+}
+.fa-trello:before {
+ content: "\f181";
+}
+.fa-female:before {
+ content: "\f182";
+}
+.fa-male:before {
+ content: "\f183";
+}
+.fa-gittip:before,
+.fa-gratipay:before {
+ content: "\f184";
+}
+.fa-sun-o:before {
+ content: "\f185";
+}
+.fa-moon-o:before {
+ content: "\f186";
+}
+.fa-archive:before {
+ content: "\f187";
+}
+.fa-bug:before {
+ content: "\f188";
+}
+.fa-vk:before {
+ content: "\f189";
+}
+.fa-weibo:before {
+ content: "\f18a";
+}
+.fa-renren:before {
+ content: "\f18b";
+}
+.fa-pagelines:before {
+ content: "\f18c";
+}
+.fa-stack-exchange:before {
+ content: "\f18d";
+}
+.fa-arrow-circle-o-right:before {
+ content: "\f18e";
+}
+.fa-arrow-circle-o-left:before {
+ content: "\f190";
+}
+.fa-toggle-left:before,
+.fa-caret-square-o-left:before {
+ content: "\f191";
+}
+.fa-dot-circle-o:before {
+ content: "\f192";
+}
+.fa-wheelchair:before {
+ content: "\f193";
+}
+.fa-vimeo-square:before {
+ content: "\f194";
+}
+.fa-turkish-lira:before,
+.fa-try:before {
+ content: "\f195";
+}
+.fa-plus-square-o:before {
+ content: "\f196";
+}
+.fa-space-shuttle:before {
+ content: "\f197";
+}
+.fa-slack:before {
+ content: "\f198";
+}
+.fa-envelope-square:before {
+ content: "\f199";
+}
+.fa-wordpress:before {
+ content: "\f19a";
+}
+.fa-openid:before {
+ content: "\f19b";
+}
+.fa-institution:before,
+.fa-bank:before,
+.fa-university:before {
+ content: "\f19c";
+}
+.fa-mortar-board:before,
+.fa-graduation-cap:before {
+ content: "\f19d";
+}
+.fa-yahoo:before {
+ content: "\f19e";
+}
+.fa-google:before {
+ content: "\f1a0";
+}
+.fa-reddit:before {
+ content: "\f1a1";
+}
+.fa-reddit-square:before {
+ content: "\f1a2";
+}
+.fa-stumbleupon-circle:before {
+ content: "\f1a3";
+}
+.fa-stumbleupon:before {
+ content: "\f1a4";
+}
+.fa-delicious:before {
+ content: "\f1a5";
+}
+.fa-digg:before {
+ content: "\f1a6";
+}
+.fa-pied-piper:before {
+ content: "\f1a7";
+}
+.fa-pied-piper-alt:before {
+ content: "\f1a8";
+}
+.fa-drupal:before {
+ content: "\f1a9";
+}
+.fa-joomla:before {
+ content: "\f1aa";
+}
+.fa-language:before {
+ content: "\f1ab";
+}
+.fa-fax:before {
+ content: "\f1ac";
+}
+.fa-building:before {
+ content: "\f1ad";
+}
+.fa-child:before {
+ content: "\f1ae";
+}
+.fa-paw:before {
+ content: "\f1b0";
+}
+.fa-spoon:before {
+ content: "\f1b1";
+}
+.fa-cube:before {
+ content: "\f1b2";
+}
+.fa-cubes:before {
+ content: "\f1b3";
+}
+.fa-behance:before {
+ content: "\f1b4";
+}
+.fa-behance-square:before {
+ content: "\f1b5";
+}
+.fa-steam:before {
+ content: "\f1b6";
+}
+.fa-steam-square:before {
+ content: "\f1b7";
+}
+.fa-recycle:before {
+ content: "\f1b8";
+}
+.fa-automobile:before,
+.fa-car:before {
+ content: "\f1b9";
+}
+.fa-cab:before,
+.fa-taxi:before {
+ content: "\f1ba";
+}
+.fa-tree:before {
+ content: "\f1bb";
+}
+.fa-spotify:before {
+ content: "\f1bc";
+}
+.fa-deviantart:before {
+ content: "\f1bd";
+}
+.fa-soundcloud:before {
+ content: "\f1be";
+}
+.fa-database:before {
+ content: "\f1c0";
+}
+.fa-file-pdf-o:before {
+ content: "\f1c1";
+}
+.fa-file-word-o:before {
+ content: "\f1c2";
+}
+.fa-file-excel-o:before {
+ content: "\f1c3";
+}
+.fa-file-powerpoint-o:before {
+ content: "\f1c4";
+}
+.fa-file-photo-o:before,
+.fa-file-picture-o:before,
+.fa-file-image-o:before {
+ content: "\f1c5";
+}
+.fa-file-zip-o:before,
+.fa-file-archive-o:before {
+ content: "\f1c6";
+}
+.fa-file-sound-o:before,
+.fa-file-audio-o:before {
+ content: "\f1c7";
+}
+.fa-file-movie-o:before,
+.fa-file-video-o:before {
+ content: "\f1c8";
+}
+.fa-file-code-o:before {
+ content: "\f1c9";
+}
+.fa-vine:before {
+ content: "\f1ca";
+}
+.fa-codepen:before {
+ content: "\f1cb";
+}
+.fa-jsfiddle:before {
+ content: "\f1cc";
+}
+.fa-life-bouy:before,
+.fa-life-buoy:before,
+.fa-life-saver:before,
+.fa-support:before,
+.fa-life-ring:before {
+ content: "\f1cd";
+}
+.fa-circle-o-notch:before {
+ content: "\f1ce";
+}
+.fa-ra:before,
+.fa-rebel:before {
+ content: "\f1d0";
+}
+.fa-ge:before,
+.fa-empire:before {
+ content: "\f1d1";
+}
+.fa-git-square:before {
+ content: "\f1d2";
+}
+.fa-git:before {
+ content: "\f1d3";
+}
+.fa-hacker-news:before {
+ content: "\f1d4";
+}
+.fa-tencent-weibo:before {
+ content: "\f1d5";
+}
+.fa-qq:before {
+ content: "\f1d6";
+}
+.fa-wechat:before,
+.fa-weixin:before {
+ content: "\f1d7";
+}
+.fa-send:before,
+.fa-paper-plane:before {
+ content: "\f1d8";
+}
+.fa-send-o:before,
+.fa-paper-plane-o:before {
+ content: "\f1d9";
+}
+.fa-history:before {
+ content: "\f1da";
+}
+.fa-genderless:before,
+.fa-circle-thin:before {
+ content: "\f1db";
+}
+.fa-header:before {
+ content: "\f1dc";
+}
+.fa-paragraph:before {
+ content: "\f1dd";
+}
+.fa-sliders:before {
+ content: "\f1de";
+}
+.fa-share-alt:before {
+ content: "\f1e0";
+}
+.fa-share-alt-square:before {
+ content: "\f1e1";
+}
+.fa-bomb:before {
+ content: "\f1e2";
+}
+.fa-soccer-ball-o:before,
+.fa-futbol-o:before {
+ content: "\f1e3";
+}
+.fa-tty:before {
+ content: "\f1e4";
+}
+.fa-binoculars:before {
+ content: "\f1e5";
+}
+.fa-plug:before {
+ content: "\f1e6";
+}
+.fa-slideshare:before {
+ content: "\f1e7";
+}
+.fa-twitch:before {
+ content: "\f1e8";
+}
+.fa-yelp:before {
+ content: "\f1e9";
+}
+.fa-newspaper-o:before {
+ content: "\f1ea";
+}
+.fa-wifi:before {
+ content: "\f1eb";
+}
+.fa-calculator:before {
+ content: "\f1ec";
+}
+.fa-paypal:before {
+ content: "\f1ed";
+}
+.fa-google-wallet:before {
+ content: "\f1ee";
+}
+.fa-cc-visa:before {
+ content: "\f1f0";
+}
+.fa-cc-mastercard:before {
+ content: "\f1f1";
+}
+.fa-cc-discover:before {
+ content: "\f1f2";
+}
+.fa-cc-amex:before {
+ content: "\f1f3";
+}
+.fa-cc-paypal:before {
+ content: "\f1f4";
+}
+.fa-cc-stripe:before {
+ content: "\f1f5";
+}
+.fa-bell-slash:before {
+ content: "\f1f6";
+}
+.fa-bell-slash-o:before {
+ content: "\f1f7";
+}
+.fa-trash:before {
+ content: "\f1f8";
+}
+.fa-copyright:before {
+ content: "\f1f9";
+}
+.fa-at:before {
+ content: "\f1fa";
+}
+.fa-eyedropper:before {
+ content: "\f1fb";
+}
+.fa-paint-brush:before {
+ content: "\f1fc";
+}
+.fa-birthday-cake:before {
+ content: "\f1fd";
+}
+.fa-area-chart:before {
+ content: "\f1fe";
+}
+.fa-pie-chart:before {
+ content: "\f200";
+}
+.fa-line-chart:before {
+ content: "\f201";
+}
+.fa-lastfm:before {
+ content: "\f202";
+}
+.fa-lastfm-square:before {
+ content: "\f203";
+}
+.fa-toggle-off:before {
+ content: "\f204";
+}
+.fa-toggle-on:before {
+ content: "\f205";
+}
+.fa-bicycle:before {
+ content: "\f206";
+}
+.fa-bus:before {
+ content: "\f207";
+}
+.fa-ioxhost:before {
+ content: "\f208";
+}
+.fa-angellist:before {
+ content: "\f209";
+}
+.fa-cc:before {
+ content: "\f20a";
+}
+.fa-shekel:before,
+.fa-sheqel:before,
+.fa-ils:before {
+ content: "\f20b";
+}
+.fa-meanpath:before {
+ content: "\f20c";
+}
+.fa-buysellads:before {
+ content: "\f20d";
+}
+.fa-connectdevelop:before {
+ content: "\f20e";
+}
+.fa-dashcube:before {
+ content: "\f210";
+}
+.fa-forumbee:before {
+ content: "\f211";
+}
+.fa-leanpub:before {
+ content: "\f212";
+}
+.fa-sellsy:before {
+ content: "\f213";
+}
+.fa-shirtsinbulk:before {
+ content: "\f214";
+}
+.fa-simplybuilt:before {
+ content: "\f215";
+}
+.fa-skyatlas:before {
+ content: "\f216";
+}
+.fa-cart-plus:before {
+ content: "\f217";
+}
+.fa-cart-arrow-down:before {
+ content: "\f218";
+}
+.fa-diamond:before {
+ content: "\f219";
+}
+.fa-ship:before {
+ content: "\f21a";
+}
+.fa-user-secret:before {
+ content: "\f21b";
+}
+.fa-motorcycle:before {
+ content: "\f21c";
+}
+.fa-street-view:before {
+ content: "\f21d";
+}
+.fa-heartbeat:before {
+ content: "\f21e";
+}
+.fa-venus:before {
+ content: "\f221";
+}
+.fa-mars:before {
+ content: "\f222";
+}
+.fa-mercury:before {
+ content: "\f223";
+}
+.fa-transgender:before {
+ content: "\f224";
+}
+.fa-transgender-alt:before {
+ content: "\f225";
+}
+.fa-venus-double:before {
+ content: "\f226";
+}
+.fa-mars-double:before {
+ content: "\f227";
+}
+.fa-venus-mars:before {
+ content: "\f228";
+}
+.fa-mars-stroke:before {
+ content: "\f229";
+}
+.fa-mars-stroke-v:before {
+ content: "\f22a";
+}
+.fa-mars-stroke-h:before {
+ content: "\f22b";
+}
+.fa-neuter:before {
+ content: "\f22c";
+}
+.fa-facebook-official:before {
+ content: "\f230";
+}
+.fa-pinterest-p:before {
+ content: "\f231";
+}
+.fa-whatsapp:before {
+ content: "\f232";
+}
+.fa-server:before {
+ content: "\f233";
+}
+.fa-user-plus:before {
+ content: "\f234";
+}
+.fa-user-times:before {
+ content: "\f235";
+}
+.fa-hotel:before,
+.fa-bed:before {
+ content: "\f236";
+}
+.fa-viacoin:before {
+ content: "\f237";
+}
+.fa-train:before {
+ content: "\f238";
+}
+.fa-subway:before {
+ content: "\f239";
+}
+.fa-medium:before {
+ content: "\f23a";
+}
diff --git a/tools/infra-dashboard/css/fullcalendar.css b/tools/infra-dashboard/css/fullcalendar.css
new file mode 100644
index 00000000..89684cb3
--- /dev/null
+++ b/tools/infra-dashboard/css/fullcalendar.css
@@ -0,0 +1,1260 @@
+/*!
+ * FullCalendar v2.7.2 Stylesheet
+ * Docs & License: http://fullcalendar.io/
+ * (c) 2016 Adam Shaw
+ */
+
+
+.fc {
+ direction: ltr;
+ text-align: left;
+}
+
+.fc-rtl {
+ text-align: right;
+}
+
+body .fc { /* extra precedence to overcome jqui */
+ font-size: 1em;
+}
+
+
+/* Colors
+--------------------------------------------------------------------------------------------------*/
+
+.fc-unthemed th,
+.fc-unthemed td,
+.fc-unthemed thead,
+.fc-unthemed tbody,
+.fc-unthemed .fc-divider,
+.fc-unthemed .fc-row,
+.fc-unthemed .fc-content, /* for gutter border */
+.fc-unthemed .fc-popover {
+ border-color: #ddd;
+}
+
+.fc-unthemed .fc-popover {
+ background-color: #fff;
+}
+
+.fc-unthemed .fc-divider,
+.fc-unthemed .fc-popover .fc-header {
+ background: #eee;
+}
+
+.fc-unthemed .fc-popover .fc-header .fc-close {
+ color: #666;
+}
+
+.fc-unthemed .fc-today {
+ background: #fcf8e3;
+}
+
+.fc-highlight { /* when user is selecting cells */
+ background: #bce8f1;
+ opacity: .3;
+ filter: alpha(opacity=30); /* for IE */
+}
+
+.fc-bgevent { /* default look for background events */
+ background: rgb(143, 223, 130);
+ opacity: .3;
+ filter: alpha(opacity=30); /* for IE */
+}
+
+.fc-nonbusiness { /* default look for non-business-hours areas */
+ /* will inherit .fc-bgevent's styles */
+ background: #d7d7d7;
+}
+
+
+/* Icons (inline elements with styled text that mock arrow icons)
+--------------------------------------------------------------------------------------------------*/
+
+.fc-icon {
+ display: inline-block;
+ height: 1em;
+ line-height: 1em;
+ font-size: 1em;
+ text-align: center;
+ overflow: hidden;
+ font-family: "Courier New", Courier, monospace;
+
+ /* don't allow browser text-selection */
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+
+/*
+Acceptable font-family overrides for individual icons:
+ "Arial", sans-serif
+ "Times New Roman", serif
+
+NOTE: use percentage font sizes or else old IE chokes
+*/
+
+.fc-icon:after {
+ position: relative;
+}
+
+.fc-icon-left-single-arrow:after {
+ content: "\02039";
+ font-weight: bold;
+ font-size: 200%;
+ top: -7%;
+}
+
+.fc-icon-right-single-arrow:after {
+ content: "\0203A";
+ font-weight: bold;
+ font-size: 200%;
+ top: -7%;
+}
+
+.fc-icon-left-double-arrow:after {
+ content: "\000AB";
+ font-size: 160%;
+ top: -7%;
+}
+
+.fc-icon-right-double-arrow:after {
+ content: "\000BB";
+ font-size: 160%;
+ top: -7%;
+}
+
+.fc-icon-left-triangle:after {
+ content: "\25C4";
+ font-size: 125%;
+ top: 3%;
+}
+
+.fc-icon-right-triangle:after {
+ content: "\25BA";
+ font-size: 125%;
+ top: 3%;
+}
+
+.fc-icon-down-triangle:after {
+ content: "\25BC";
+ font-size: 125%;
+ top: 2%;
+}
+
+.fc-icon-x:after {
+ content: "\000D7";
+ font-size: 200%;
+ top: 6%;
+}
+
+
+/* Buttons (styled <button> tags, normalized to work cross-browser)
+--------------------------------------------------------------------------------------------------*/
+
+.fc button {
+ /* force height to include the border and padding */
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+
+ /* dimensions */
+ margin: 0;
+ height: 2.1em;
+ padding: 0 .6em;
+
+ /* text & cursor */
+ font-size: 1em; /* normalize */
+ white-space: nowrap;
+ cursor: pointer;
+}
+
+/* Firefox has an annoying inner border */
+.fc button::-moz-focus-inner { margin: 0; padding: 0; }
+
+.fc-state-default { /* non-theme */
+ border: 1px solid;
+}
+
+.fc-state-default.fc-corner-left { /* non-theme */
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+}
+
+.fc-state-default.fc-corner-right { /* non-theme */
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+}
+
+/* icons in buttons */
+
+.fc button .fc-icon { /* non-theme */
+ position: relative;
+ top: -0.05em; /* seems to be a good adjustment across browsers */
+ margin: 0 .2em;
+ vertical-align: middle;
+}
+
+/*
+ button states
+ borrowed from twitter bootstrap (http://twitter.github.com/bootstrap/)
+*/
+
+.fc-state-default {
+ background-color: #f5f5f5;
+ background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+ background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
+ background-repeat: repeat-x;
+ border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ color: #333;
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.fc-state-hover,
+.fc-state-down,
+.fc-state-active,
+.fc-state-disabled {
+ color: #333333;
+ background-color: #e6e6e6;
+}
+
+.fc-state-hover {
+ color: #333333;
+ text-decoration: none;
+ background-position: 0 -15px;
+ -webkit-transition: background-position 0.1s linear;
+ -moz-transition: background-position 0.1s linear;
+ -o-transition: background-position 0.1s linear;
+ transition: background-position 0.1s linear;
+}
+
+.fc-state-down,
+.fc-state-active {
+ background-color: #cccccc;
+ background-image: none;
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.fc-state-disabled {
+ cursor: default;
+ background-image: none;
+ opacity: 0.65;
+ filter: alpha(opacity=65);
+ box-shadow: none;
+}
+
+
+/* Buttons Groups
+--------------------------------------------------------------------------------------------------*/
+
+.fc-button-group {
+ display: inline-block;
+}
+
+/*
+every button that is not first in a button group should scootch over one pixel and cover the
+previous button's border...
+*/
+
+.fc .fc-button-group > * { /* extra precedence b/c buttons have margin set to zero */
+ float: left;
+ margin: 0 0 0 -1px;
+}
+
+.fc .fc-button-group > :first-child { /* same */
+ margin-left: 0;
+}
+
+
+/* Popover
+--------------------------------------------------------------------------------------------------*/
+
+.fc-popover {
+ position: absolute;
+ box-shadow: 0 2px 6px rgba(0,0,0,.15);
+}
+
+.fc-popover .fc-header { /* TODO: be more consistent with fc-head/fc-body */
+ padding: 2px 4px;
+}
+
+.fc-popover .fc-header .fc-title {
+ margin: 0 2px;
+}
+
+.fc-popover .fc-header .fc-close {
+ cursor: pointer;
+}
+
+.fc-ltr .fc-popover .fc-header .fc-title,
+.fc-rtl .fc-popover .fc-header .fc-close {
+ float: left;
+}
+
+.fc-rtl .fc-popover .fc-header .fc-title,
+.fc-ltr .fc-popover .fc-header .fc-close {
+ float: right;
+}
+
+/* unthemed */
+
+.fc-unthemed .fc-popover {
+ border-width: 1px;
+ border-style: solid;
+}
+
+.fc-unthemed .fc-popover .fc-header .fc-close {
+ font-size: .9em;
+ margin-top: 2px;
+}
+
+/* jqui themed */
+
+.fc-popover > .ui-widget-header + .ui-widget-content {
+ border-top: 0; /* where they meet, let the header have the border */
+}
+
+
+/* Misc Reusable Components
+--------------------------------------------------------------------------------------------------*/
+
+.fc-divider {
+ border-style: solid;
+ border-width: 1px;
+}
+
+hr.fc-divider {
+ height: 0;
+ margin: 0;
+ padding: 0 0 2px; /* height is unreliable across browsers, so use padding */
+ border-width: 1px 0;
+}
+
+.fc-clear {
+ clear: both;
+}
+
+.fc-bg,
+.fc-bgevent-skeleton,
+.fc-highlight-skeleton,
+.fc-helper-skeleton {
+ /* these element should always cling to top-left/right corners */
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+}
+
+.fc-bg {
+ bottom: 0; /* strech bg to bottom edge */
+}
+
+.fc-bg table {
+ height: 100%; /* strech bg to bottom edge */
+}
+
+
+/* Tables
+--------------------------------------------------------------------------------------------------*/
+
+.fc table {
+ width: 100%;
+ table-layout: fixed;
+ border-collapse: collapse;
+ border-spacing: 0;
+ font-size: 1em; /* normalize cross-browser */
+}
+
+.fc th {
+ text-align: center;
+}
+
+.fc th,
+.fc td {
+ border-style: solid;
+ border-width: 1px;
+ padding: 0;
+ vertical-align: top;
+}
+
+.fc td.fc-today {
+ border-style: double; /* overcome neighboring borders */
+}
+
+
+/* Fake Table Rows
+--------------------------------------------------------------------------------------------------*/
+
+.fc .fc-row { /* extra precedence to overcome themes w/ .ui-widget-content forcing a 1px border */
+ /* no visible border by default. but make available if need be (scrollbar width compensation) */
+ border-style: solid;
+ border-width: 0;
+}
+
+.fc-row table {
+ /* don't put left/right border on anything within a fake row.
+ the outer tbody will worry about this */
+ border-left: 0 hidden transparent;
+ border-right: 0 hidden transparent;
+
+ /* no bottom borders on rows */
+ border-bottom: 0 hidden transparent;
+}
+
+.fc-row:first-child table {
+ border-top: 0 hidden transparent; /* no top border on first row */
+}
+
+
+/* Day Row (used within the header and the DayGrid)
+--------------------------------------------------------------------------------------------------*/
+
+.fc-row {
+ position: relative;
+}
+
+.fc-row .fc-bg {
+ z-index: 1;
+}
+
+/* highlighting cells & background event skeleton */
+
+.fc-row .fc-bgevent-skeleton,
+.fc-row .fc-highlight-skeleton {
+ bottom: 0; /* stretch skeleton to bottom of row */
+}
+
+.fc-row .fc-bgevent-skeleton table,
+.fc-row .fc-highlight-skeleton table {
+ height: 100%; /* stretch skeleton to bottom of row */
+}
+
+.fc-row .fc-highlight-skeleton td,
+.fc-row .fc-bgevent-skeleton td {
+ border-color: transparent;
+}
+
+.fc-row .fc-bgevent-skeleton {
+ z-index: 2;
+
+}
+
+.fc-row .fc-highlight-skeleton {
+ z-index: 3;
+}
+
+/*
+row content (which contains day/week numbers and events) as well as "helper" (which contains
+temporary rendered events).
+*/
+
+.fc-row .fc-content-skeleton {
+ position: relative;
+ z-index: 4;
+ padding-bottom: 2px; /* matches the space above the events */
+}
+
+.fc-row .fc-helper-skeleton {
+ z-index: 5;
+}
+
+.fc-row .fc-content-skeleton td,
+.fc-row .fc-helper-skeleton td {
+ /* see-through to the background below */
+ background: none; /* in case <td>s are globally styled */
+ border-color: transparent;
+
+ /* don't put a border between events and/or the day number */
+ border-bottom: 0;
+}
+
+.fc-row .fc-content-skeleton tbody td, /* cells with events inside (so NOT the day number cell) */
+.fc-row .fc-helper-skeleton tbody td {
+ /* don't put a border between event cells */
+ border-top: 0;
+}
+
+
+/* Scrolling Container
+--------------------------------------------------------------------------------------------------*/
+
+.fc-scroller {
+ -webkit-overflow-scrolling: touch;
+}
+
+/* TODO: move to agenda/basic */
+.fc-scroller > .fc-day-grid,
+.fc-scroller > .fc-time-grid {
+ position: relative; /* re-scope all positions */
+ width: 100%; /* hack to force re-sizing this inner element when scrollbars appear/disappear */
+}
+
+
+/* Global Event Styles
+--------------------------------------------------------------------------------------------------*/
+
+.fc-event {
+ position: relative; /* for resize handle and other inner positioning */
+ display: block; /* make the <a> tag block */
+ font-size: .85em;
+ line-height: 1.3;
+ border-radius: 3px;
+ border: 1px solid #3a87ad; /* default BORDER color */
+ background-color: #3a87ad; /* default BACKGROUND color */
+ font-weight: normal; /* undo jqui's ui-widget-header bold */
+}
+
+/* overpower some of bootstrap's and jqui's styles on <a> tags */
+.fc-event,
+.fc-event:hover,
+.ui-widget .fc-event {
+ color: #fff; /* default TEXT color */
+ text-decoration: none; /* if <a> has an href */
+}
+
+.fc-event[href],
+.fc-event.fc-draggable {
+ cursor: pointer; /* give events with links and draggable events a hand mouse pointer */
+}
+
+.fc-not-allowed, /* causes a "warning" cursor. applied on body */
+.fc-not-allowed .fc-event { /* to override an event's custom cursor */
+ cursor: not-allowed;
+}
+
+.fc-event .fc-bg { /* the generic .fc-bg already does position */
+ z-index: 1;
+ background: #fff;
+ opacity: .25;
+ filter: alpha(opacity=25); /* for IE */
+}
+
+.fc-event .fc-content {
+ position: relative;
+ z-index: 2;
+}
+
+/* resizer (cursor AND touch devices) */
+
+.fc-event .fc-resizer {
+ position: absolute;
+ z-index: 4;
+}
+
+/* resizer (touch devices) */
+
+.fc-event .fc-resizer {
+ display: none;
+}
+
+.fc-event.fc-allow-mouse-resize .fc-resizer,
+.fc-event.fc-selected .fc-resizer {
+ /* only show when hovering or selected (with touch) */
+ display: block;
+}
+
+/* hit area */
+
+.fc-event.fc-selected .fc-resizer:before {
+ /* 40x40 touch area */
+ content: "";
+ position: absolute;
+ z-index: 9999; /* user of this util can scope within a lower z-index */
+ top: 50%;
+ left: 50%;
+ width: 40px;
+ height: 40px;
+ margin-left: -20px;
+ margin-top: -20px;
+}
+
+
+/* Event Selection (only for touch devices)
+--------------------------------------------------------------------------------------------------*/
+
+.fc-event.fc-selected {
+ z-index: 9999 !important; /* overcomes inline z-index */
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+}
+
+.fc-event.fc-selected.fc-dragging {
+ box-shadow: 0 2px 7px rgba(0, 0, 0, 0.3);
+}
+
+
+/* Horizontal Events
+--------------------------------------------------------------------------------------------------*/
+
+/* bigger touch area when selected */
+.fc-h-event.fc-selected:before {
+ content: "";
+ position: absolute;
+ z-index: 3; /* below resizers */
+ top: -10px;
+ bottom: -10px;
+ left: 0;
+ right: 0;
+}
+
+/* events that are continuing to/from another week. kill rounded corners and butt up against edge */
+
+.fc-ltr .fc-h-event.fc-not-start,
+.fc-rtl .fc-h-event.fc-not-end {
+ margin-left: 0;
+ border-left-width: 0;
+ padding-left: 1px; /* replace the border with padding */
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.fc-ltr .fc-h-event.fc-not-end,
+.fc-rtl .fc-h-event.fc-not-start {
+ margin-right: 0;
+ border-right-width: 0;
+ padding-right: 1px; /* replace the border with padding */
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+/* resizer (cursor AND touch devices) */
+
+/* left resizer */
+.fc-ltr .fc-h-event .fc-start-resizer,
+.fc-rtl .fc-h-event .fc-end-resizer {
+ cursor: w-resize;
+ left: -1px; /* overcome border */
+}
+
+/* right resizer */
+.fc-ltr .fc-h-event .fc-end-resizer,
+.fc-rtl .fc-h-event .fc-start-resizer {
+ cursor: e-resize;
+ right: -1px; /* overcome border */
+}
+
+/* resizer (mouse devices) */
+
+.fc-h-event.fc-allow-mouse-resize .fc-resizer {
+ width: 7px;
+ top: -1px; /* overcome top border */
+ bottom: -1px; /* overcome bottom border */
+}
+
+/* resizer (touch devices) */
+
+.fc-h-event.fc-selected .fc-resizer {
+ /* 8x8 little dot */
+ border-radius: 4px;
+ border-width: 1px;
+ width: 6px;
+ height: 6px;
+ border-style: solid;
+ border-color: inherit;
+ background: #fff;
+ /* vertically center */
+ top: 50%;
+ margin-top: -4px;
+}
+
+/* left resizer */
+.fc-ltr .fc-h-event.fc-selected .fc-start-resizer,
+.fc-rtl .fc-h-event.fc-selected .fc-end-resizer {
+ margin-left: -4px; /* centers the 8x8 dot on the left edge */
+}
+
+/* right resizer */
+.fc-ltr .fc-h-event.fc-selected .fc-end-resizer,
+.fc-rtl .fc-h-event.fc-selected .fc-start-resizer {
+ margin-right: -4px; /* centers the 8x8 dot on the right edge */
+}
+
+
+/* DayGrid events
+----------------------------------------------------------------------------------------------------
+We use the full "fc-day-grid-event" class instead of using descendants because the event won't
+be a descendant of the grid when it is being dragged.
+*/
+
+.fc-day-grid-event {
+ margin: 1px 2px 0; /* spacing between events and edges */
+ padding: 0 1px;
+}
+
+.fc-day-grid-event.fc-selected:after {
+ content: "";
+ position: absolute;
+ z-index: 1; /* same z-index as fc-bg, behind text */
+ /* overcome the borders */
+ top: -1px;
+ right: -1px;
+ bottom: -1px;
+ left: -1px;
+ /* darkening effect */
+ background: #000;
+ opacity: .25;
+ filter: alpha(opacity=25); /* for IE */
+}
+
+.fc-day-grid-event .fc-content { /* force events to be one-line tall */
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.fc-day-grid-event .fc-time {
+ font-weight: bold;
+}
+
+/* resizer (cursor devices) */
+
+/* left resizer */
+.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer,
+.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer {
+ margin-left: -2px; /* to the day cell's edge */
+}
+
+/* right resizer */
+.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer,
+.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer {
+ margin-right: -2px; /* to the day cell's edge */
+}
+
+
+/* Event Limiting
+--------------------------------------------------------------------------------------------------*/
+
+/* "more" link that represents hidden events */
+
+a.fc-more {
+ margin: 1px 3px;
+ font-size: .85em;
+ cursor: pointer;
+ text-decoration: none;
+}
+
+a.fc-more:hover {
+ text-decoration: underline;
+}
+
+.fc-limited { /* rows and cells that are hidden because of a "more" link */
+ display: none;
+}
+
+/* popover that appears when "more" link is clicked */
+
+.fc-day-grid .fc-row {
+ z-index: 1; /* make the "more" popover one higher than this */
+}
+
+.fc-more-popover {
+ z-index: 2;
+ width: 220px;
+}
+
+.fc-more-popover .fc-event-container {
+ padding: 10px;
+}
+
+
+/* Now Indicator
+--------------------------------------------------------------------------------------------------*/
+
+.fc-now-indicator {
+ position: absolute;
+ border: 0 solid red;
+}
+
+
+/* Utilities
+--------------------------------------------------------------------------------------------------*/
+
+.fc-unselectable {
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-touch-callout: none;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+/* Toolbar
+--------------------------------------------------------------------------------------------------*/
+
+.fc-toolbar {
+ text-align: center;
+ margin-bottom: 1em;
+}
+
+.fc-toolbar .fc-left {
+ float: left;
+}
+
+.fc-toolbar .fc-right {
+ float: right;
+}
+
+.fc-toolbar .fc-center {
+ display: inline-block;
+}
+
+/* the things within each left/right/center section */
+.fc .fc-toolbar > * > * { /* extra precedence to override button border margins */
+ float: left;
+ margin-left: .75em;
+}
+
+/* the first thing within each left/center/right section */
+.fc .fc-toolbar > * > :first-child { /* extra precedence to override button border margins */
+ margin-left: 0;
+}
+
+/* title text */
+
+.fc-toolbar h2 {
+ margin: 0;
+}
+
+/* button layering (for border precedence) */
+
+.fc-toolbar button {
+ position: relative;
+}
+
+.fc-toolbar .fc-state-hover,
+.fc-toolbar .ui-state-hover {
+ z-index: 2;
+}
+
+.fc-toolbar .fc-state-down {
+ z-index: 3;
+}
+
+.fc-toolbar .fc-state-active,
+.fc-toolbar .ui-state-active {
+ z-index: 4;
+}
+
+.fc-toolbar button:focus {
+ z-index: 5;
+}
+
+
+/* View Structure
+--------------------------------------------------------------------------------------------------*/
+
+/* undo twitter bootstrap's box-sizing rules. normalizes positioning techniques */
+/* don't do this for the toolbar because we'll want bootstrap to style those buttons as some pt */
+.fc-view-container *,
+.fc-view-container *:before,
+.fc-view-container *:after {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+.fc-view, /* scope positioning and z-index's for everything within the view */
+.fc-view > table { /* so dragged elements can be above the view's main element */
+ position: relative;
+ z-index: 1;
+}
+
+/* BasicView
+--------------------------------------------------------------------------------------------------*/
+
+/* day row structure */
+
+.fc-basicWeek-view .fc-content-skeleton,
+.fc-basicDay-view .fc-content-skeleton {
+ /* we are sure there are no day numbers in these views, so... */
+ padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */
+ padding-bottom: 1em; /* ensure a space at bottom of cell for user selecting/clicking */
+}
+
+.fc-basic-view .fc-body .fc-row {
+ min-height: 4em; /* ensure that all rows are at least this tall */
+}
+
+/* a "rigid" row will take up a constant amount of height because content-skeleton is absolute */
+
+.fc-row.fc-rigid {
+ overflow: hidden;
+}
+
+.fc-row.fc-rigid .fc-content-skeleton {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+}
+
+/* week and day number styling */
+
+.fc-basic-view .fc-week-number,
+.fc-basic-view .fc-day-number {
+ padding: 0 2px;
+}
+
+.fc-basic-view td.fc-week-number span,
+.fc-basic-view td.fc-day-number {
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
+.fc-basic-view .fc-week-number {
+ text-align: center;
+}
+
+.fc-basic-view .fc-week-number span {
+ /* work around the way we do column resizing and ensure a minimum width */
+ display: inline-block;
+ min-width: 1.25em;
+}
+
+.fc-ltr .fc-basic-view .fc-day-number {
+ text-align: right;
+}
+
+.fc-rtl .fc-basic-view .fc-day-number {
+ text-align: left;
+}
+
+.fc-day-number.fc-other-month {
+ opacity: 0.3;
+ filter: alpha(opacity=30); /* for IE */
+ /* opacity with small font can sometimes look too faded
+ might want to set the 'color' property instead
+ making day-numbers bold also fixes the problem */
+}
+
+/* AgendaView all-day area
+--------------------------------------------------------------------------------------------------*/
+
+.fc-agenda-view .fc-day-grid {
+ position: relative;
+ z-index: 2; /* so the "more.." popover will be over the time grid */
+}
+
+.fc-agenda-view .fc-day-grid .fc-row {
+ min-height: 3em; /* all-day section will never get shorter than this */
+}
+
+.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton {
+ padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */
+ padding-bottom: 1em; /* give space underneath events for clicking/selecting days */
+}
+
+
+/* TimeGrid axis running down the side (for both the all-day area and the slot area)
+--------------------------------------------------------------------------------------------------*/
+
+.fc .fc-axis { /* .fc to overcome default cell styles */
+ vertical-align: middle;
+ padding: 0 4px;
+ white-space: nowrap;
+}
+
+.fc-ltr .fc-axis {
+ text-align: right;
+}
+
+.fc-rtl .fc-axis {
+ text-align: left;
+}
+
+.ui-widget td.fc-axis {
+ font-weight: normal; /* overcome jqui theme making it bold */
+}
+
+
+/* TimeGrid Structure
+--------------------------------------------------------------------------------------------------*/
+
+.fc-time-grid-container, /* so scroll container's z-index is below all-day */
+.fc-time-grid { /* so slats/bg/content/etc positions get scoped within here */
+ position: relative;
+ z-index: 1;
+}
+
+.fc-time-grid {
+ min-height: 100%; /* so if height setting is 'auto', .fc-bg stretches to fill height */
+}
+
+.fc-time-grid table { /* don't put outer borders on slats/bg/content/etc */
+ border: 0 hidden transparent;
+}
+
+.fc-time-grid > .fc-bg {
+ z-index: 1;
+}
+
+.fc-time-grid .fc-slats,
+.fc-time-grid > hr { /* the <hr> AgendaView injects when grid is shorter than scroller */
+ position: relative;
+ z-index: 2;
+}
+
+.fc-time-grid .fc-content-col {
+ position: relative; /* because now-indicator lives directly inside */
+}
+
+.fc-time-grid .fc-content-skeleton {
+ position: absolute;
+ z-index: 3;
+ top: 0;
+ left: 0;
+ right: 0;
+}
+
+/* divs within a cell within the fc-content-skeleton */
+
+.fc-time-grid .fc-business-container {
+ position: relative;
+ z-index: 1;
+}
+
+.fc-time-grid .fc-bgevent-container {
+ position: relative;
+ z-index: 2;
+}
+
+.fc-time-grid .fc-highlight-container {
+ position: relative;
+ z-index: 3;
+}
+
+.fc-time-grid .fc-event-container {
+ position: relative;
+ z-index: 4;
+}
+
+.fc-time-grid .fc-now-indicator-line {
+ z-index: 5;
+}
+
+.fc-time-grid .fc-helper-container { /* also is fc-event-container */
+ position: relative;
+ z-index: 6;
+}
+
+
+/* TimeGrid Slats (lines that run horizontally)
+--------------------------------------------------------------------------------------------------*/
+
+.fc-time-grid .fc-slats td {
+ height: 1.5em;
+ border-bottom: 0; /* each cell is responsible for its top border */
+}
+
+.fc-time-grid .fc-slats .fc-minor td {
+ border-top-style: dotted;
+}
+
+.fc-time-grid .fc-slats .ui-widget-content { /* for jqui theme */
+ background: none; /* see through to fc-bg */
+}
+
+
+/* TimeGrid Highlighting Slots
+--------------------------------------------------------------------------------------------------*/
+
+.fc-time-grid .fc-highlight-container { /* a div within a cell within the fc-highlight-skeleton */
+ position: relative; /* scopes the left/right of the fc-highlight to be in the column */
+}
+
+.fc-time-grid .fc-highlight {
+ position: absolute;
+ left: 0;
+ right: 0;
+ /* top and bottom will be in by JS */
+}
+
+
+/* TimeGrid Event Containment
+--------------------------------------------------------------------------------------------------*/
+
+.fc-ltr .fc-time-grid .fc-event-container { /* space on the sides of events for LTR (default) */
+ margin: 0 2.5% 0 2px;
+}
+
+.fc-rtl .fc-time-grid .fc-event-container { /* space on the sides of events for RTL */
+ margin: 0 2px 0 2.5%;
+}
+
+.fc-time-grid .fc-event,
+.fc-time-grid .fc-bgevent {
+ position: absolute;
+ z-index: 1; /* scope inner z-index's */
+}
+
+.fc-time-grid .fc-bgevent {
+ /* background events always span full width */
+ left: 0;
+ right: 0;
+}
+
+
+/* Generic Vertical Event
+--------------------------------------------------------------------------------------------------*/
+
+.fc-v-event.fc-not-start { /* events that are continuing from another day */
+ /* replace space made by the top border with padding */
+ border-top-width: 0;
+ padding-top: 1px;
+
+ /* remove top rounded corners */
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.fc-v-event.fc-not-end {
+ /* replace space made by the top border with padding */
+ border-bottom-width: 0;
+ padding-bottom: 1px;
+
+ /* remove bottom rounded corners */
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+
+/* TimeGrid Event Styling
+----------------------------------------------------------------------------------------------------
+We use the full "fc-time-grid-event" class instead of using descendants because the event won't
+be a descendant of the grid when it is being dragged.
+*/
+
+.fc-time-grid-event {
+ overflow: hidden; /* don't let the bg flow over rounded corners */
+}
+
+.fc-time-grid-event.fc-selected {
+ /* need to allow touch resizers to extend outside event's bounding box */
+ /* common fc-selected styles hide the fc-bg, so don't need this anyway */
+ overflow: visible;
+}
+
+.fc-time-grid-event.fc-selected .fc-bg {
+ display: none; /* hide semi-white background, to appear darker */
+}
+
+.fc-time-grid-event .fc-content {
+ overflow: hidden; /* for when .fc-selected */
+}
+
+.fc-time-grid-event .fc-time,
+.fc-time-grid-event .fc-title {
+ padding: 0 1px;
+}
+
+.fc-time-grid-event .fc-time {
+ font-size: .85em;
+ white-space: nowrap;
+}
+
+/* short mode, where time and title are on the same line */
+
+.fc-time-grid-event.fc-short .fc-content {
+ /* don't wrap to second line (now that contents will be inline) */
+ white-space: nowrap;
+}
+
+.fc-time-grid-event.fc-short .fc-time,
+.fc-time-grid-event.fc-short .fc-title {
+ /* put the time and title on the same line */
+ display: inline-block;
+ vertical-align: top;
+}
+
+.fc-time-grid-event.fc-short .fc-time span {
+ display: none; /* don't display the full time text... */
+}
+
+.fc-time-grid-event.fc-short .fc-time:before {
+ content: attr(data-start); /* ...instead, display only the start time */
+}
+
+.fc-time-grid-event.fc-short .fc-time:after {
+ content: "\000A0-\000A0"; /* seperate with a dash, wrapped in nbsp's */
+}
+
+.fc-time-grid-event.fc-short .fc-title {
+ font-size: .85em; /* make the title text the same size as the time */
+ padding: 0; /* undo padding from above */
+}
+
+/* resizer (cursor device) */
+
+.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer {
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: 8px;
+ overflow: hidden;
+ line-height: 8px;
+ font-size: 11px;
+ font-family: monospace;
+ text-align: center;
+ cursor: s-resize;
+}
+
+.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer:after {
+ content: "=";
+}
+
+/* resizer (touch device) */
+
+.fc-time-grid-event.fc-selected .fc-resizer {
+ /* 10x10 dot */
+ border-radius: 5px;
+ border-width: 1px;
+ width: 8px;
+ height: 8px;
+ border-style: solid;
+ border-color: inherit;
+ background: #fff;
+ /* horizontally center */
+ left: 50%;
+ margin-left: -5px;
+ /* center on the bottom edge */
+ bottom: -5px;
+}
+
+
+/* Now Indicator
+--------------------------------------------------------------------------------------------------*/
+
+.fc-time-grid .fc-now-indicator-line {
+ border-top-width: 1px;
+ left: 0;
+ right: 0;
+}
+
+/* arrow on axis */
+
+.fc-time-grid .fc-now-indicator-arrow {
+ margin-top: -5px; /* vertically center on top coordinate */
+}
+
+.fc-ltr .fc-time-grid .fc-now-indicator-arrow {
+ left: 0;
+ /* triangle pointing right... */
+ border-width: 5px 0 5px 6px;
+ border-top-color: transparent;
+ border-bottom-color: transparent;
+}
+
+.fc-rtl .fc-time-grid .fc-now-indicator-arrow {
+ right: 0;
+ /* triangle pointing left... */
+ border-width: 5px 6px 5px 0;
+ border-top-color: transparent;
+ border-bottom-color: transparent;
+}
diff --git a/tools/infra-dashboard/css/fullcalendar.print.css b/tools/infra-dashboard/css/fullcalendar.print.css
new file mode 100644
index 00000000..af884fc8
--- /dev/null
+++ b/tools/infra-dashboard/css/fullcalendar.print.css
@@ -0,0 +1,208 @@
+/*!
+ * FullCalendar v2.7.2 Print Stylesheet
+ * Docs & License: http://fullcalendar.io/
+ * (c) 2016 Adam Shaw
+ */
+
+/*
+ * Include this stylesheet on your page to get a more printer-friendly calendar.
+ * When including this stylesheet, use the media='print' attribute of the <link> tag.
+ * Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css.
+ */
+
+.fc {
+ max-width: 100% !important;
+}
+
+
+/* Global Event Restyling
+--------------------------------------------------------------------------------------------------*/
+
+.fc-event {
+ background: #fff !important;
+ color: #000 !important;
+ page-break-inside: avoid;
+}
+
+.fc-event .fc-resizer {
+ display: none;
+}
+
+
+/* Table & Day-Row Restyling
+--------------------------------------------------------------------------------------------------*/
+
+th,
+td,
+hr,
+thead,
+tbody,
+.fc-row {
+ border-color: #ccc !important;
+ background: #fff !important;
+}
+
+/* kill the overlaid, absolutely-positioned components */
+/* common... */
+.fc-bg,
+.fc-bgevent-skeleton,
+.fc-highlight-skeleton,
+.fc-helper-skeleton,
+/* for timegrid. within cells within table skeletons... */
+.fc-bgevent-container,
+.fc-business-container,
+.fc-highlight-container,
+.fc-helper-container {
+ display: none;
+}
+
+/* don't force a min-height on rows (for DayGrid) */
+.fc tbody .fc-row {
+ height: auto !important; /* undo height that JS set in distributeHeight */
+ min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */
+}
+
+.fc tbody .fc-row .fc-content-skeleton {
+ position: static; /* undo .fc-rigid */
+ padding-bottom: 0 !important; /* use a more border-friendly method for this... */
+}
+
+.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */
+ padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */
+}
+
+.fc tbody .fc-row .fc-content-skeleton table {
+ /* provides a min-height for the row, but only effective for IE, which exaggerates this value,
+ making it look more like 3em. for other browers, it will already be this tall */
+ height: 1em;
+}
+
+
+/* Undo month-view event limiting. Display all events and hide the "more" links
+--------------------------------------------------------------------------------------------------*/
+
+.fc-more-cell,
+.fc-more {
+ display: none !important;
+}
+
+.fc tr.fc-limited {
+ display: table-row !important;
+}
+
+.fc td.fc-limited {
+ display: table-cell !important;
+}
+
+.fc-popover {
+ display: none; /* never display the "more.." popover in print mode */
+}
+
+
+/* TimeGrid Restyling
+--------------------------------------------------------------------------------------------------*/
+
+/* undo the min-height 100% trick used to fill the container's height */
+.fc-time-grid {
+ min-height: 0 !important;
+}
+
+/* don't display the side axis at all ("all-day" and time cells) */
+.fc-agenda-view .fc-axis {
+ display: none;
+}
+
+/* don't display the horizontal lines */
+.fc-slats,
+.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */
+ display: none !important; /* important overrides inline declaration */
+}
+
+/* let the container that holds the events be naturally positioned and create real height */
+.fc-time-grid .fc-content-skeleton {
+ position: static;
+}
+
+/* in case there are no events, we still want some height */
+.fc-time-grid .fc-content-skeleton table {
+ height: 4em;
+}
+
+/* kill the horizontal spacing made by the event container. event margins will be done below */
+.fc-time-grid .fc-event-container {
+ margin: 0 !important;
+}
+
+
+/* TimeGrid *Event* Restyling
+--------------------------------------------------------------------------------------------------*/
+
+/* naturally position events, vertically stacking them */
+.fc-time-grid .fc-event {
+ position: static !important;
+ margin: 3px 2px !important;
+}
+
+/* for events that continue to a future day, give the bottom border back */
+.fc-time-grid .fc-event.fc-not-end {
+ border-bottom-width: 1px !important;
+}
+
+/* indicate the event continues via "..." text */
+.fc-time-grid .fc-event.fc-not-end:after {
+ content: "...";
+}
+
+/* for events that are continuations from previous days, give the top border back */
+.fc-time-grid .fc-event.fc-not-start {
+ border-top-width: 1px !important;
+}
+
+/* indicate the event is a continuation via "..." text */
+.fc-time-grid .fc-event.fc-not-start:before {
+ content: "...";
+}
+
+/* time */
+
+/* undo a previous declaration and let the time text span to a second line */
+.fc-time-grid .fc-event .fc-time {
+ white-space: normal !important;
+}
+
+/* hide the the time that is normally displayed... */
+.fc-time-grid .fc-event .fc-time span {
+ display: none;
+}
+
+/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */
+.fc-time-grid .fc-event .fc-time:after {
+ content: attr(data-full);
+}
+
+
+/* Vertical Scroller & Containers
+--------------------------------------------------------------------------------------------------*/
+
+/* kill the scrollbars and allow natural height */
+.fc-scroller,
+.fc-day-grid-container, /* these divs might be assigned height, which we need to cleared */
+.fc-time-grid-container { /* */
+ overflow: visible !important;
+ height: auto !important;
+}
+
+/* kill the horizontal border/padding used to compensate for scrollbars */
+.fc-row {
+ border: 0 !important;
+ margin: 0 !important;
+}
+
+
+/* Button Controls
+--------------------------------------------------------------------------------------------------*/
+
+.fc-button-group,
+.fc button {
+ display: none; /* don't display any button-related controls */
+}
diff --git a/tools/infra-dashboard/css/highslide.min.css b/tools/infra-dashboard/css/highslide.min.css
new file mode 100644
index 00000000..83bac0f4
--- /dev/null
+++ b/tools/infra-dashboard/css/highslide.min.css
@@ -0,0 +1,793 @@
+.highslide-header a,
+.highslide-loading {
+ text-transform: uppercase;
+ text-decoration: none;
+ font-weight: 700
+}
+.highslide-container div {
+ font-family: Verdana, Helvetica;
+ font-size: 10pt
+}
+.highslide-container table {
+ background: 0 0
+}
+.highslide {
+ outline: 0;
+ text-decoration: none
+}
+.highslide img {
+ border: 2px solid silver
+}
+.highslide:hover img {
+ border-color: gray
+}
+.highslide-active-anchor img {
+ visibility: hidden
+}
+.highslide-gallery .highslide-active-anchor img {
+ border-color: #000;
+ visibility: visible;
+ cursor: default
+}
+.highslide-image {
+ border-width: 2px;
+ border-style: solid;
+ border-color: #fff;
+ background: gray
+}
+.highslide-outline,
+.highslide-wrapper {
+ background: #fff
+}
+.glossy-dark {
+ background: #111
+}
+.highslide-number {
+ font-weight: 700;
+ color: gray;
+ font-size: .9em
+}
+.highslide-caption {
+ display: none;
+ font-size: 1em;
+ padding: 5px
+}
+.highslide-heading {
+ display: none;
+ font-weight: 700;
+ margin: .4em
+}
+.highslide-dimming {
+ position: absolute;
+ background: #000
+}
+a.highslide-full-expand {
+ background: url(/media/com_demo/graphics/fullexpand.gif) no-repeat;
+ display: block;
+ margin: 0 10px 10px 0;
+ width: 34px;
+ height: 34px
+}
+.highslide-loading {
+ display: block;
+ color: #000;
+ font-size: 9px;
+ padding: 3px 3px 3px 22px;
+ border: 1px solid #fff;
+ background-color: #fff;
+ background-image: url(./media/loader.white.gif);
+ background-repeat: no-repeat;
+ background-position: 3px 1px
+}
+a.highslide-credits,
+a.highslide-credits i {
+ padding: 2px;
+ color: silver;
+ text-decoration: none;
+ font-size: 10px
+}
+a.highslide-credits:hover,
+a.highslide-credits:hover i {
+ color: #fff;
+ background-color: gray
+}
+.highslide-move,
+.highslide-move * {
+ cursor: move
+}
+.highslide-viewport {
+ display: none;
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+ background: 0 0;
+ left: 0;
+ top: 0
+}
+.hidden-container,
+.highslide-overlay {
+ display: none
+}
+.closebutton {
+ position: relative;
+ top: -15px;
+ left: 15px;
+ width: 30px;
+ height: 30px;
+ cursor: pointer;
+ background: url(/media/com_demo/graphics/close.png)
+}
+.highslide-gallery ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0
+}
+.highslide-gallery ul li {
+ display: block;
+ position: relative;
+ float: left;
+ width: 106px;
+ height: 106px;
+ border: 1px solid silver;
+ background: #ededed;
+ margin: 2px;
+ line-height: 0;
+ overflow: hidden
+}
+.highslide-gallery ul a {
+ position: absolute;
+ top: 50%;
+ left: 50%
+}
+.highslide-gallery ul img {
+ position: relative;
+ top: -50%;
+ left: -50%
+}
+html>body .highslide-gallery ul li {
+ display: table;
+ text-align: center
+}
+html>body .highslide-gallery ul a {
+ position: static;
+ display: table-cell;
+ vertical-align: middle
+}
+html>body .highslide-gallery ul img {
+ position: static
+}
+.highslide-controls {
+ width: 195px;
+ height: 40px;
+ background: url(/media/com_demo/graphics/controlbar-white.gif) 0 -90px no-repeat;
+ margin: 20px 15px 10px 0
+}
+.highslide-controls ul {
+ position: relative;
+ left: 15px;
+ height: 40px;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ background: url(/media/com_demo/graphics/controlbar-white.gif) right -90px no-repeat
+}
+.highslide-controls li {
+ float: left;
+ padding: 5px 0;
+ margin: 0;
+ list-style: none
+}
+.highslide-controls a {
+ background-image: url(/media/com_demo/graphics/controlbar-white.gif);
+ display: block;
+ float: left;
+ height: 30px;
+ width: 30px;
+ outline: 0
+}
+.highslide-controls a.disabled,
+.highslide-controls a.disabled span {
+ cursor: default
+}
+.highslide-controls a span {
+ display: none;
+ cursor: pointer
+}
+.highslide-controls .highslide-previous a {
+ background-position: 0 0
+}
+.highslide-controls .highslide-previous a:hover {
+ background-position: 0 -30px
+}
+.highslide-controls .highslide-previous a.disabled {
+ background-position: 0 -60px!important
+}
+.highslide-controls .highslide-play a {
+ background-position: -30px 0
+}
+.highslide-controls .highslide-play a:hover {
+ background-position: -30px -30px
+}
+.highslide-controls .highslide-play a.disabled {
+ background-position: -30px -60px!important
+}
+.highslide-controls .highslide-pause a {
+ background-position: -60px 0
+}
+.highslide-controls .highslide-pause a:hover {
+ background-position: -60px -30px
+}
+.highslide-controls .highslide-next a {
+ background-position: -90px 0
+}
+.highslide-controls .highslide-next a:hover {
+ background-position: -90px -30px
+}
+.highslide-controls .highslide-next a.disabled {
+ background-position: -90px -60px!important
+}
+.highslide-controls .highslide-move a {
+ background-position: -90pt 0
+}
+.highslide-controls .highslide-move a:hover {
+ background-position: -90pt -30px
+}
+.highslide-controls .highslide-full-expand a {
+ background-position: -150px 0
+}
+.highslide-controls .highslide-full-expand a:hover {
+ background-position: -150px -30px
+}
+.highslide-controls .highslide-full-expand a.disabled {
+ background-position: -150px -60px!important
+}
+.highslide-controls .highslide-close a {
+ background-position: -180px 0
+}
+.highslide-controls .highslide-close a:hover {
+ background-position: -180px -30px
+}
+.highslide-maincontent {
+ display: none
+}
+.highslide-html {
+ background-color: #fff
+}
+.highslide-html-content {
+ display: none;
+ width: 25pc;
+ padding: 0 5px 5px
+}
+.highslide-header {
+ padding-bottom: 5px
+}
+.highslide-header ul {
+ margin: 0;
+ padding: 0;
+ text-align: right
+}
+.highslide-header ul li {
+ display: inline;
+ padding-left: 1em
+}
+.highslide-header ul li.highslide-next,
+.highslide-header ul li.highslide-previous {
+ display: none
+}
+.highslide-header a {
+ color: gray
+}
+.highslide-header a:hover {
+ color: #000
+}
+.highslide-header .highslide-move a {
+ cursor: move
+}
+.highslide-footer {
+ height: 1pc
+}
+.highslide-footer .highslide-resize {
+ display: block;
+ float: right;
+ margin-top: 5px;
+ height: 11px;
+ width: 11px;
+ background: url(/media/com_demo/graphics/resize.gif) no-repeat
+}
+.highslide-footer .highslide-resize span {
+ display: none
+}
+.highslide-resize {
+ cursor: nw-resize
+}
+.draggable-header .highslide-header {
+ height: 18px;
+ border-bottom: 1px solid #ddd
+}
+.draggable-header .highslide-heading {
+ position: absolute;
+ margin: 2px .4em
+}
+.draggable-header .highslide-header .highslide-move {
+ cursor: move;
+ display: block;
+ height: 1pc;
+ position: absolute;
+ right: 24px;
+ top: 0;
+ width: 100%;
+ z-index: 1
+}
+.draggable-header .highslide-header .highslide-move * {
+ display: none
+}
+.draggable-header .highslide-header .highslide-close {
+ position: absolute;
+ right: 2px;
+ top: 2px;
+ z-index: 5;
+ padding: 0
+}
+.draggable-header .highslide-header .highslide-close a {
+ display: block;
+ height: 1pc;
+ width: 1pc;
+ background-image: url(/media/com_demo/graphics/closeX.png)
+}
+.draggable-header .highslide-header .highslide-close a:hover {
+ background-position: 0 1pc
+}
+.draggable-header .highslide-header .highslide-close span {
+ display: none
+}
+.draggable-header .highslide-maincontent {
+ padding-top: 1em
+}
+.titlebar .highslide-header {
+ height: 18px;
+ border-bottom: 1px solid #ddd
+}
+.titlebar .highslide-heading {
+ position: absolute;
+ width: 90%;
+ margin: 1px 0 1px 5px;
+ color: #666
+}
+.titlebar .highslide-header .highslide-move {
+ cursor: move;
+ display: block;
+ height: 1pc;
+ position: absolute;
+ right: 24px;
+ top: 0;
+ width: 100%;
+ z-index: 1
+}
+.controls-in-heading .highslide-controls .highslide-move,
+.no-footer .highslide-footer,
+.text-controls .highslide-move,
+.titlebar .highslide-header .highslide-move * {
+ display: none
+}
+.titlebar .highslide-header li {
+ position: relative;
+ top: 3px;
+ z-index: 2;
+ padding: 0 0 0 1em
+}
+.titlebar .highslide-maincontent {
+ padding-top: 1em
+}
+.wide-border {
+ background: #fff
+}
+.wide-border .highslide-image {
+ border-width: 10px
+}
+.wide-border .highslide-caption {
+ padding: 0 10px 10px
+}
+.borderless .highslide-image {
+ border: none
+}
+.borderless .highslide-caption {
+ border-bottom: 1px solid #fff;
+ border-top: 1px solid #fff;
+ background: silver
+}
+.outer-glow {
+ background: #444
+}
+.outer-glow .highslide-image {
+ border: 5px solid #444
+}
+.outer-glow .highslide-caption {
+ border: 5px solid #444;
+ border-top: none;
+ padding: 5px;
+ background-color: gray
+}
+.colored-border {
+ background: #fff
+}
+.colored-border .highslide-image {
+ border: 2px solid green
+}
+.colored-border .highslide-caption {
+ border: 2px solid green;
+ border-top: none
+}
+.dark {
+ background: #111
+}
+.dark .highslide-image {
+ border-color: #000 #000 #202020;
+ background: gray
+}
+.dark .highslide-caption {
+ color: #fff;
+ background: #111
+}
+.dark .highslide-controls,
+.dark .highslide-controls a,
+.dark .highslide-controls ul {
+ background-image: url(/media/com_demo/graphics/controlbar-black-border.gif)
+}
+.floating-caption .highslide-caption {
+ position: absolute;
+ padding: 1em 0 0;
+ background: 0 0;
+ color: #fff;
+ border: none;
+ font-weight: 700
+}
+.controls-in-heading .highslide-heading {
+ color: gray;
+ font-weight: 700;
+ height: 20px;
+ overflow: hidden;
+ cursor: default;
+ padding: 0 0 0 22px;
+ margin: 0;
+ background: url(/media/com_demo/graphics/icon.gif) 0 1px no-repeat
+}
+.controls-in-heading .highslide-controls {
+ width: 105px;
+ height: 20px;
+ position: relative;
+ margin: 0;
+ top: -23px;
+ left: 7px;
+ background: 0 0
+}
+.controls-in-heading .highslide-controls ul {
+ position: static;
+ height: 20px;
+ background: 0 0
+}
+.controls-in-heading .highslide-controls li {
+ padding: 0
+}
+.controls-in-heading .highslide-controls a {
+ background-image: url(/media/com_demo/graphics/controlbar-white-small.gif);
+ height: 20px;
+ width: 20px
+}
+.controls-in-heading .highslide-controls .highslide-previous a {
+ background-position: 0 0
+}
+.controls-in-heading .highslide-controls .highslide-previous a:hover {
+ background-position: 0 -20px
+}
+.controls-in-heading .highslide-controls .highslide-previous a.disabled {
+ background-position: 0 -40px!important
+}
+.controls-in-heading .highslide-controls .highslide-play a {
+ background-position: -20px 0
+}
+.controls-in-heading .highslide-controls .highslide-play a:hover {
+ background-position: -20px -20px
+}
+.controls-in-heading .highslide-controls .highslide-play a.disabled {
+ background-position: -20px -40px!important
+}
+.controls-in-heading .highslide-controls .highslide-pause a {
+ background-position: -40px 0
+}
+.controls-in-heading .highslide-controls .highslide-pause a:hover {
+ background-position: -40px -20px
+}
+.controls-in-heading .highslide-controls .highslide-next a {
+ background-position: -60px 0
+}
+.controls-in-heading .highslide-controls .highslide-next a:hover {
+ background-position: -60px -20px
+}
+.controls-in-heading .highslide-controls .highslide-next a.disabled {
+ background-position: -60px -40px!important
+}
+.controls-in-heading .highslide-controls .highslide-full-expand a {
+ background-position: -75pt 0
+}
+.controls-in-heading .highslide-controls .highslide-full-expand a:hover {
+ background-position: -75pt -20px
+}
+.controls-in-heading .highslide-controls .highslide-full-expand a.disabled {
+ background-position: -75pt -40px!important
+}
+.controls-in-heading .highslide-controls .highslide-close a {
+ background-position: -90pt 0
+}
+.controls-in-heading .highslide-controls .highslide-close a:hover {
+ background-position: -90pt -20px
+}
+.text-controls .highslide-controls {
+ width: auto;
+ height: auto;
+ margin: 0;
+ text-align: center;
+ background: 0 0
+}
+.text-controls ul {
+ position: static;
+ background: 0 0;
+ height: auto;
+ left: 0
+}
+.text-controls li {
+ background-image: url(/media/com_demo/graphics/controlbar-text-buttons.png);
+ background-position: right top!important;
+ padding: 0;
+ margin-left: 15px;
+ display: block;
+ width: auto
+}
+.text-controls a {
+ background: url(/media/com_demo/graphics/controlbar-text-buttons.png) no-repeat;
+ background-position: left top!important;
+ position: relative;
+ left: -10px;
+ display: block;
+ width: auto;
+ height: auto;
+ text-decoration: none!important
+}
+.text-controls a span {
+ background: url(/media/com_demo/graphics/controlbar-text-buttons.png) no-repeat;
+ margin: 1px 2px 1px 10px;
+ display: block;
+ min-width: 4em;
+ height: 18px;
+ line-height: 18px;
+ padding: 1px 0 1px 18px;
+ color: #333;
+ font-family: "Trebuchet MS", Arial, sans-serif;
+ font-size: 9pt;
+ font-weight: 700;
+ white-space: nowrap
+}
+.text-controls .highslide-next {
+ margin-right: 1em
+}
+.text-controls .highslide-full-expand a span {
+ min-width: 0;
+ margin: 1px 0;
+ padding: 1px 0 1px 10px
+}
+.text-controls .highslide-close a span {
+ min-width: 0
+}
+.text-controls a:hover span {
+ color: #000
+}
+.text-controls a.disabled span {
+ color: #999
+}
+.text-controls .highslide-previous span {
+ background-position: 0 -40px
+}
+.text-controls .highslide-previous a.disabled {
+ background-position: left top!important
+}
+.text-controls .highslide-previous a.disabled span {
+ background-position: 0 -140px
+}
+.text-controls .highslide-play span {
+ background-position: 0 -60px
+}
+.text-controls .highslide-play a.disabled {
+ background-position: left top!important
+}
+.text-controls .highslide-play a.disabled span {
+ background-position: 0 -10pc
+}
+.text-controls .highslide-pause span {
+ background-position: 0 -5pc
+}
+.text-controls .highslide-next span {
+ background-position: 0 -75pt
+}
+.text-controls .highslide-next a.disabled {
+ background-position: left top!important
+}
+.text-controls .highslide-next a.disabled span {
+ background-position: 0 -200px
+}
+.text-controls .highslide-full-expand span {
+ background: 0 0
+}
+.text-controls .highslide-full-expand a.disabled {
+ background-position: left top!important
+}
+.text-controls .highslide-close span {
+ background-position: 0 -90pt
+}
+.highslide-thumbstrip {
+ height: 100%
+}
+.highslide-thumbstrip div {
+ overflow: hidden
+}
+.highslide-thumbstrip table {
+ position: relative;
+ padding: 0;
+ border-collapse: collapse
+}
+.highslide-thumbstrip td {
+ padding: 1px
+}
+.highslide-thumbstrip a {
+ outline: 0
+}
+.highslide-thumbstrip img {
+ display: block;
+ border: 1px solid gray;
+ margin: 0 auto
+}
+.highslide-thumbstrip .highslide-active-anchor img {
+ visibility: visible
+}
+.highslide-thumbstrip .highslide-marker {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-width: 0;
+ border-style: solid;
+ border-color: transparent
+}
+.highslide-thumbstrip-horizontal div {
+ width: auto
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-up {
+ display: none;
+ position: absolute;
+ top: 3px;
+ left: 3px;
+ width: 25px;
+ height: 42px
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-up div {
+ margin-bottom: 10px;
+ cursor: pointer;
+ background: url(/media/com_demo/graphics/scrollarrows.png) left center no-repeat;
+ height: 42px
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-down {
+ display: none;
+ position: absolute;
+ top: 3px;
+ right: 3px;
+ width: 25px;
+ height: 42px
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-down div {
+ margin-bottom: 10px;
+ cursor: pointer;
+ background: url(/media/com_demo/graphics/scrollarrows.png) center right no-repeat;
+ height: 42px
+}
+.highslide-thumbstrip-horizontal table {
+ margin: 2px 0 10px
+}
+.highslide-viewport .highslide-thumbstrip-horizontal table {
+ margin-left: 10px
+}
+.highslide-thumbstrip-horizontal img {
+ width: auto;
+ height: 40px
+}
+.highslide-thumbstrip-horizontal .highslide-marker {
+ top: 47px;
+ border-left-width: 6px;
+ border-right-width: 6px;
+ border-bottom: 6px solid gray
+}
+.highslide-viewport .highslide-thumbstrip-horizontal .highslide-marker {
+ margin-left: 10px
+}
+.dark .highslide-thumbstrip-horizontal .highslide-marker,
+.highslide-viewport .highslide-thumbstrip-horizontal .highslide-marker {
+ border-bottom-color: #fff!important
+}
+.highslide-thumbstrip-vertical-overlay {
+ overflow: hidden!important
+}
+.highslide-thumbstrip-vertical div {
+ height: 100%
+}
+.highslide-thumbstrip-vertical a {
+ display: block
+}
+.highslide-thumbstrip-vertical .highslide-scroll-up {
+ display: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 25px
+}
+.highslide-thumbstrip-vertical .highslide-scroll-up div {
+ margin-left: 10px;
+ cursor: pointer;
+ background: url(/media/com_demo/graphics/scrollarrows.png) top center no-repeat;
+ height: 25px
+}
+.highslide-thumbstrip-vertical .highslide-scroll-down {
+ display: none;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 25px
+}
+.highslide-thumbstrip-vertical .highslide-scroll-down div {
+ margin-left: 10px;
+ cursor: pointer;
+ background: url(/media/com_demo/graphics/scrollarrows.png) bottom center no-repeat;
+ height: 25px
+}
+.highslide-thumbstrip-vertical table {
+ margin: 10px 0 0 10px
+}
+.highslide-thumbstrip-vertical img {
+ max-width: 60px
+}
+.highslide-thumbstrip-vertical .highslide-marker {
+ left: 0;
+ margin-top: 8px;
+ border-top-width: 6px;
+ border-bottom-width: 6px;
+ border-left: 6px solid gray
+}
+.dark .highslide-thumbstrip-vertical .highslide-marker,
+.highslide-viewport .highslide-thumbstrip-vertical .highslide-marker {
+ border-left-color: #fff
+}
+.highslide-viewport .highslide-thumbstrip-float {
+ overflow: auto
+}
+.highslide-thumbstrip-float ul {
+ margin: 2px 0;
+ padding: 0
+}
+.highslide-thumbstrip-float li {
+ display: block;
+ height: 60px;
+ margin: 0 2px;
+ list-style: none;
+ float: left
+}
+.highslide-thumbstrip-float img {
+ display: inline;
+ border-color: silver;
+ max-height: 56px
+}
+.highslide-thumbstrip-float .highslide-active-anchor img {
+ border-color: #000
+}
+.highslide-thumbstrip-float .highslide-marker,
+.highslide-thumbstrip-float .highslide-scroll-down div,
+.highslide-thumbstrip-float .highslide-scroll-up div {
+ display: none
+} \ No newline at end of file
diff --git a/tools/infra-dashboard/css/opnfv.css b/tools/infra-dashboard/css/opnfv.css
new file mode 100644
index 00000000..8b2711ae
--- /dev/null
+++ b/tools/infra-dashboard/css/opnfv.css
@@ -0,0 +1,2479 @@
+* {
+ margin: 0;
+ padding: 0;
+}
+.clearfix {
+ display: inline-block;
+}
+.clearfix:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+* html .clearfix {
+ height: 1%;
+}
+.clearfix {
+ display: block;
+}
+.clearleft,
+.clearl,
+.cleft {
+ clear: left;
+}
+.clearright,
+.clearr,
+.cright {
+ clear: right;
+}
+.clear,
+.clearboth,
+.clearall {
+ clear: both;
+}
+.floatleft,
+.fleft,
+.floatl {
+ float: left;
+ margin: 0 10px 5px 0;
+}
+.floatright,
+.fright,
+.floatr {
+ float: right;
+ margin: 0 0 5px 10px;
+}
+#skip a:link,
+#skip a:hover,
+#skip a:visited {
+ position: absolute;
+ left: -10000px;
+ top: auto;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+}
+#skip a:active,
+#skip a:focus {
+ position: static;
+ width: auto;
+ height: auto;
+}
+div.view div.views-admin-links {
+ width: auto;
+}
+div.block {
+ position: relative;
+}
+div.block .edit {
+ display: none;
+ position: absolute;
+ right: -20px;
+ top: -5px;
+ z-index: 40;
+ padding: 3px 8px 0;
+ font-size: 10px;
+ line-height: 16px;
+ background-color: white;
+ border: 1px solid #cccccc;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ -moz-box-shadow: 0 1px 3px #888888;
+ -webkit-box-shadow: -1px 1px 2px #666666;
+}
+div.block .edit a {
+ display: block;
+ border: 0;
+ padding: 0;
+ margin: 0;
+}
+div.block:hover .edit {
+ display: block;
+}
+* {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.container {
+ max-width: 68em;
+ margin-left: auto;
+ margin-right: auto;
+ margin-left: auto;
+ margin-right: auto;
+ width: 68em;
+}
+.container:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+@media screen and (max-width: 1088px) {
+ .container {
+ width: auto;
+ }
+}
+.no-sidebars #content {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 100%;
+}
+.no-sidebars #content:last-child {
+ margin-right: 0;
+}
+@media screen and (max-width: 768px) {
+ .no-sidebars #content {
+ float: left;
+ display: block;
+ margin-right: 3.22581%;
+ width: 100%;
+ }
+ .no-sidebars #content:last-child {
+ margin-right: 0;
+ }
+}
+@media screen and (max-width: 480px) {
+ .no-sidebars #content {
+ float: left;
+ display: block;
+ margin-right: 6.66667%;
+ width: 100%;
+ }
+ .no-sidebars #content:last-child {
+ margin-right: 0;
+ }
+}
+.one-sidebar.sidebar-second #content {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 72.88136%;
+}
+.one-sidebar.sidebar-second #content:last-child {
+ margin-right: 0;
+}
+@media screen and (max-width: 768px) {
+ .one-sidebar.sidebar-second #content {
+ float: left;
+ display: block;
+ margin-right: 3.22581%;
+ width: 74.19355%;
+ }
+ .one-sidebar.sidebar-second #content:last-child {
+ margin-right: 0;
+ }
+}
+@media screen and (max-width: 480px) {
+ .one-sidebar.sidebar-second #content {
+ float: left;
+ display: block;
+ margin-right: 6.66667%;
+ width: 100%;
+ }
+ .one-sidebar.sidebar-second #content:last-child {
+ margin-right: 0;
+ }
+}
+.one-sidebar.sidebar-first #content {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 79.66102%;
+ margin-left: 20.33898%;
+}
+.one-sidebar.sidebar-first #content:last-child {
+ margin-right: 0;
+}
+@media screen and (max-width: 768px) {
+ .one-sidebar.sidebar-first #content {
+ float: left;
+ display: block;
+ margin-right: 3.22581%;
+ width: 74.19355%;
+ margin-left: 25.80645%;
+ }
+ .one-sidebar.sidebar-first #content:last-child {
+ margin-right: 0;
+ }
+}
+@media screen and (max-width: 480px) {
+ .one-sidebar.sidebar-first #content {
+ float: left;
+ display: block;
+ margin-right: 6.66667%;
+ width: 100%;
+ margin-left: 0%;
+ }
+ .one-sidebar.sidebar-first #content:last-child {
+ margin-right: 0;
+ }
+}
+.two-sidebars #content {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 52.54237%;
+ margin-left: 20.33898%;
+}
+.two-sidebars #content:last-child {
+ margin-right: 0;
+}
+@media screen and (max-width: 768px) {
+ .two-sidebars #content {
+ float: left;
+ display: block;
+ margin-right: 3.22581%;
+ width: 48.3871%;
+ margin-left: 25.80645%;
+ }
+ .two-sidebars #content:last-child {
+ margin-right: 0;
+ }
+}
+@media screen and (max-width: 480px) {
+ .two-sidebars #content {
+ float: left;
+ display: block;
+ margin-right: 6.66667%;
+ width: 100%;
+ margin-left: 0%;
+ }
+ .two-sidebars #content:last-child {
+ margin-right: 0;
+ }
+}
+#sidebar-first {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 18.64407%;
+ margin-left: -74.57627%;
+}
+#sidebar-first:last-child {
+ margin-right: 0;
+}
+.sidebar-first #sidebar-first {
+ margin-left: -101.69492%;
+}
+@media screen and (max-width: 768px) {
+ #sidebar-first {
+ float: left;
+ display: block;
+ margin-right: 3.22581%;
+ width: 22.58065%;
+ margin-left: -77.41935%;
+ }
+ #sidebar-first:last-child {
+ margin-right: 0;
+ }
+ .sidebar-first #sidebar-first {
+ margin-left: -103.22581%;
+ }
+}
+@media screen and (max-width: 480px) {
+ #sidebar-first {
+ float: left;
+ display: block;
+ margin-right: 6.66667%;
+ width: 100%;
+ margin-left: 0%;
+ }
+ #sidebar-first:last-child {
+ margin-right: 0;
+ }
+ .sidebar-first #sidebar-first {
+ margin-left: 0%;
+ }
+}
+#sidebar-second {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 25.42373%;
+ margin-left: 0%;
+}
+#sidebar-second:last-child {
+ margin-right: 0;
+}
+@media screen and (max-width: 768px) {
+ #sidebar-second {
+ float: left;
+ display: block;
+ margin-right: 3.22581%;
+ width: 22.58065%;
+ margin-left: 0%;
+ }
+ #sidebar-second:last-child {
+ margin-right: 0;
+ }
+}
+@media screen and (max-width: 480px) {
+ #sidebar-second {
+ float: left;
+ display: block;
+ margin-right: 6.66667%;
+ width: 100%;
+ margin-left: 0%;
+ }
+ #sidebar-second:last-child {
+ margin-right: 0;
+ }
+}
+#footer {
+ left: 0;
+ width: 100%;
+ height: 50px;
+ position: fixed;
+ bottom: 0;
+}
+#header,
+#footer,
+.mission,
+.breadcrumb,
+.node {
+ clear: both;
+}
+.inner {
+ padding: 0;
+}
+#navigation li {
+ list-style-type: none;
+ display: inline-block;
+}
+body {
+ margin: 0;
+ font: 14px/1.5em "Helvetica Neue", helvetica, Arial, sans-serif;
+ letter-spacing: 0.03em;
+}
+a:link,
+a:visited {
+ color: blue;
+ text-decoration: none;
+}
+a:hover,
+a:active {
+ color: red;
+ text-decoration: underline;
+}
+#site-name {
+ font-size: 2.2em;
+ line-height: 1.3em;
+ font-weight: 300;
+ padding: 0 0 0.5em;
+ margin: 0;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ line-height: 1.3em;
+}
+h1 {
+ font-size: 2.2em;
+ font-weight: 300;
+ padding: 0 0 0.5em;
+ margin: 0;
+}
+h2 {
+ font-size: 1.8em;
+ font-weight: 300;
+ margin-bottom: 0.75em;
+}
+h3 {
+ font-size: 1.4em;
+ margin-bottom: 1em;
+ margin-top: 1em;
+}
+h4 {
+ font-size: 1.2em;
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+h5 {
+ font-size: 1.1em;
+ font-weight: 600;
+ margin-bottom: 0;
+}
+h6 {
+ font-size: 1em;
+ font-weight: bold;
+}
+p {
+ margin: 0 0 1em 0;
+}
+ul,
+ol {
+ margin-left: 0;
+ padding-left: 0;
+}
+table {
+ width: 99%;
+}
+table tbody {
+ border-top: 0px;
+}
+table tr.even,
+table tr.odd,
+table tr {
+ border-bottom: 1px solid #ccc;
+}
+table tr.even td,
+table tr.odd td,
+table tr td {
+ padding: 10px 5px;
+ vertical-align: top;
+}
+table tr.odd {
+ background-color: white;
+}
+pre,
+code,
+tt {
+ font: 1em "andale mono", "lucida console", monospace;
+ line-height: 1.5;
+}
+pre {
+ background-color: #efefef;
+ display: block;
+ padding: 5px;
+ margin: 5px 0;
+ border: 1px solid #aaaaaa;
+}
+ul {
+ margin-left: 25px;
+ list-style-type: disc;
+}
+ul ul {
+ list-style-type: circle;
+}
+ul ul ul {
+ list-style-type: square;
+}
+ul ul ul ul {
+ list-style-type: circle;
+}
+ol {
+ list-style-type: decimal;
+}
+ol ol {
+ list-style-type: lower-alpha;
+}
+ol ol ol {
+ list-style-type: decimal;
+}
+abbr {
+ border-bottom: 1px dotted #666666;
+ cursor: help;
+ white-space: nowrap;
+}
+#edit-title {
+ font-size: 24px;
+ width: 99%;
+}
+#system-themes-form img {
+ width: 100px;
+}
+.form-item .description {
+ font-style: italic;
+ line-height: 1.2em;
+ font-size: 0.8em;
+ margin-top: 5px;
+ color: #777777;
+}
+#edit-delete {
+ color: #cc0000;
+}
+div.messages {
+ padding: 9px;
+ margin: 1em 0;
+ color: #003366;
+ background: #bbddff;
+ border: 1px solid #aaccee;
+}
+div.warning {
+ color: #884400;
+ background: #ffee66;
+ border-color: #eedd55;
+}
+div.error {
+ color: white;
+ background: #ee6633;
+ border-color: #dd5522;
+}
+div.status {
+ color: #336600;
+ background: #ccff88;
+ border-color: #bbee77;
+}
+#block-views-developer_tools-block {
+ padding-top: 48px;
+ border-top: 1px solid #cccccc;
+}
+#block-views-developer_tools-block .view-content ul {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ list-style-image: none;
+}
+#block-views-developer_tools-block .view-content ul li {
+ margin: 0;
+ padding: 0;
+ height: 100px;
+ list-style-type: none;
+ list-style-image: none;
+ float: left;
+ width: 50%;
+ margin-bottom: 60px;
+}
+#block-views-developer_tools-block .view-content ul li .views-field-title {
+ font-size: 24px;
+ color: #f15922;
+ text-align: center;
+ margin-bottom: 10px;
+ font-weight: 400;
+}
+#block-views-developer_tools-block .view-content ul li .views-field-nothing {
+ text-align: justify;
+}
+#block-views-developer_tools-block .view-content ul li .views-field-nothing a {
+ text-transform: uppercase;
+ font-weight: 400;
+ font-size: 95%;
+}
+#block-views-developer_tools-block .view-content ul .views-row-odd {
+ padding-right: 20px;
+}
+#block-views-developer_tools-block .view-content ul .views-row-even {
+ padding-left: 20px;
+}
+#edit-submit-resources {
+ margin-top: 0px;
+}
+* {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+* {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+a,
+a:link,
+a:visited {
+ color: #41ba77;
+}
+a:hover {
+ color: #373A36;
+}
+p {
+ color: #373A36;
+}
+#block-menu-menu-social-links li {
+ margin: 0;
+ padding: 0 3px 0 0;
+}
+#block-menu-menu-social-links li a {
+ display: block;
+ width: 27px;
+ height: 29px;
+ text-indent: -9999px;
+ overflow: hidden;
+ background: url(/sites/all/themes/opnfv/images/optimized/social-icons1.png) no-repeat 0 0;
+}
+#block-menu-menu-social-links li a:hover {
+ opacity: 0.7;
+}
+#block-menu-menu-social-links li .twitter {
+ background-position: -103px 0;
+}
+#block-menu-menu-social-links li .linkedin {
+ background-position: -78px 0;
+}
+#block-menu-menu-social-links li .youtube {
+ background-position: -50px 0;
+}
+#block-menu-menu-social-links li .facebook {
+ background-position: -26px 0;
+}
+#block-menu-menu-social-links li .gplus {
+ background-position: 0 0;
+}
+#block-menu-menu-social-links li .slideshare {
+ background-position: -160px 0;
+}
+#block-menu-menu-social-links li .flickr {
+ background-position: -212px 0;
+}
+#block-menu-menu-social-links li .vimeo {
+ background-position: -185px 0;
+}
+.feed-label {
+ vertical-align: text-top;
+ margin-left: 5px;
+}
+.block-menu-block a {
+ color: #2E2925;
+ font-size: 14px;
+}
+.block-menu-block a.active {
+ color: #2E2925;
+}
+#header {
+ position: relative;
+}
+@media screen and (min-width: 0) and (max-width: 460px) {
+ #header {
+ top: 0px;
+ }
+}
+#header #block-menu-menu-social-links {
+ position: relative;
+ z-index: 50;
+ float: right;
+ right: 200px;
+}
+@media screen and (min-width: 640px) and (max-width: 768px) {
+ #header #block-menu-menu-social-links {
+ top: 62px;
+ }
+}
+@media screen and (min-width: 0) and (max-width: 640px) {
+ #header #block-menu-menu-social-links {
+ right: 0;
+ top: 0;
+ }
+}
+#header #block-search-form,
+#header #search-block-form {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 100%;
+ position: relative;
+ z-index: 100;
+}
+#header #block-search-form:last-child,
+#header #search-block-form:last-child {
+ margin-right: 0;
+}
+#header #block-search-form .block-inner,
+#header #search-block-form .block-inner {
+ text-align: right;
+}
+#header #block-search-form .block-inner .container-inline,
+#header #search-block-form .block-inner .container-inline {
+ position: absolute;
+ top: -25px;
+ right: 9px;
+}
+@media screen and (min-width: 640px) and (max-width: 768px) {
+ #header #block-search-form .block-inner .container-inline,
+ #header #search-block-form .block-inner .container-inline {
+ right: 0;
+ top: 36px;
+ }
+}
+@media screen and (min-width: 0) and (max-width: 640px) {
+ #header #block-search-form .block-inner .container-inline,
+ #header #search-block-form .block-inner .container-inline {
+ top: 0;
+ }
+}
+#header #block-search-form .block-inner .form-text,
+#header #search-block-form .block-inner .form-text {
+ width: 130px;
+}
+@media screen and (min-width: 0) and (max-width: 640px) {
+ #header #block-search-form .block-inner .form-text,
+ #header #search-block-form .block-inner .form-text {
+ width: 84px;
+ }
+}
+#header #block-search-form #edit-actions .form-submit,
+#header #search-block-form #edit-actions .form-submit {
+ background: #ecedee;
+ color: #2E2925;
+ font-size: 10px;
+ border: none;
+ padding: 6px 7px;
+ top: -2px;
+ position: relative;
+}
+#header #block-search-form #edit-actions .form-submit:hover,
+#header #search-block-form #edit-actions .form-submit:hover {
+ background: #aeb2b7;
+}
+#header .menu-block-wrapper {
+ background: none;
+}
+#header .menu-block-wrapper .menu li {
+ list-style: none;
+ margin: 0;
+}
+#header .menu-block-wrapper .menu .menu-level-1 {
+ display: inline-block;
+ vertical-align: top;
+ margin: 0 0.5em 0.5em 0;
+ padding: 0;
+}
+@media screen and (min-width: 0) and (max-width: 500px) {
+ #header .menu-block-wrapper .menu .menu-level-1 {
+ width: 100%;
+ }
+ #header .menu-block-wrapper .menu .menu-level-1 a {
+ background: url(/sites/all/themes/opnfv/images/optimized/nav-arrow.png) no-repeat right center #fff;
+ }
+}
+@media screen and (min-width: 500px) and (max-width: 700px) {
+ #header .menu-block-wrapper .menu .menu-level-1 {
+ width: 48%;
+ }
+ #header .menu-block-wrapper .menu .menu-level-1 a {
+ background: url(/sites/all/themes/opnfv/images/optimized/nav-arrow.png) no-repeat right center #fff;
+ }
+}
+#header .menu-block-wrapper .menu .menu-level-1 > a {
+ display: block;
+ width: 100%;
+ margin: 0 auto 0.25em;
+ font-size: 0.9em;
+ padding: 10px 15px;
+ text-transform: uppercase;
+}
+#header .menu-block-wrapper .menu .menu-level-1 > a.selected,
+#header .menu-block-wrapper .menu .menu-level-1 > a:hover {
+ background-color: #ecedee;
+}
+.no-touch #header {
+ height: 103px;
+}
+.no-touch .site-menus-container {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 72.88136%;
+ margin-left: 27.11864%;
+}
+.no-touch .site-menus-container:last-child {
+ margin-right: 0;
+}
+@media screen and (min-width: 750px) and (max-width: 980px) {
+ .no-touch .site-menus-container {
+ height: 240px;
+ }
+}
+@media screen and (min-width: 490px) and (max-width: 750px) {
+ .no-touch .site-menus-container {
+ height: 280px;
+ }
+}
+.no-touch .logo-container {
+ position: relative;
+ z-index: 2;
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 18.64407%;
+ margin-left: 0%;
+}
+.no-touch .logo-container:last-child {
+ margin-right: 0;
+}
+.no-touch .logo-container-positioning {
+ position: relative;
+ top: 2px;
+ left: 0;
+}
+@media screen and (min-width: 0) and (max-width: 460px) {
+ .no-touch .logo-container-positioning {
+ top: 0;
+ }
+}
+.no-touch .logo-container-positioning .logo-container {
+ padding-top: 0;
+}
+@media screen and (min-width: 0) and (max-width: 460px) {
+ .no-touch .logo-container-positioning .logo-container {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 100%;
+ }
+ .no-touch .logo-container-positioning .logo-container:last-child {
+ margin-right: 0;
+ }
+ .no-touch .logo-container-positioning .logo-container #logo {
+ width: 46% !important;
+ }
+ .no-touch .logo-container-positioning .logo-container #logo img {
+ width: 100%;
+ }
+}
+.no-touch .site-menus-container-positioning {
+ position: relative;
+ top: -60px;
+ z-index: 1;
+}
+.no-touch #site-menus .menu {
+ text-align: right;
+ position: relative;
+}
+@media screen and (min-width: 0) and (max-width: 768px) {
+ .no-touch #header {
+ height: 140px;
+ }
+ .no-touch #header #site-menus .menu {
+ text-align: left;
+ }
+ .no-touch #header .site-menus-container {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 100%;
+ margin-left: 0%;
+ }
+ .no-touch #header .site-menus-container:last-child {
+ margin-right: 0;
+ }
+ .no-touch #header .site-menus-container-positioning {
+ position: relative;
+ top: 0;
+ padding-top: 0;
+ }
+}
+@media screen and (min-width: 490px) and (max-width: 700px) {
+ .no-touch #header {
+ height: 260px;
+ }
+}
+@media screen and (min-width: 0) and (max-width: 500px) {
+ .no-touch #header {
+ height: auto;
+ }
+}
+.collapsible-menu #header {
+ top: 0;
+}
+.collapsible-menu #header #block-search-form .container-inline,
+.collapsible-menu #header #search-block-form .container-inline {
+ position: absolute;
+ top: 110px;
+ right: 0;
+}
+.collapsible-menu #header #site-name {
+ position: absolute;
+ top: 0;
+}
+.collapsible-menu #header .logo-container-positioning {
+ position: relative;
+ top: -40px;
+ left: -10px;
+ padding: 0 0 25px;
+}
+.collapsible-menu #header .logo-container-positioning .container {
+ padding-top: 15px;
+}
+@media screen and (min-width: 0) and (max-width: 460px) {
+ .collapsible-menu #header .logo-container-positioning #logo {
+ width: 48% !important;
+ margin-top: 25px;
+ height: 55px;
+ }
+ .collapsible-menu #header .logo-container-positioning #logo img {
+ width: 100%;
+ }
+}
+.collapsible-menu #header .menu-block-wrapper .menu .menu-level-1 {
+ border-left: 4px solid #A3D783;
+}
+.collapsible-menu #header .menu-block-wrapper .menu .menu-level-1 > a {
+ background-color: #2E2925;
+ color: #fff;
+ margin-bottom: 0;
+ padding-bottom: 0.5em;
+}
+.collapsible-menu #header .site-menus-container-positioning {
+ background: #ecedee;
+ color: #fff;
+ padding-top: 0;
+}
+.collapsible-menu #header .site-menus-container ul {
+ margin-top: 0.75em;
+}
+.collapsible-menu #header .menu-button-wrapper {
+ position: relative;
+}
+.collapsible-menu #header .menu-button-wrapper .menu-button {
+ position: absolute;
+ right: -15px;
+ top: 10px;
+ border: 1px solid #ccc;
+ background: #ddd;
+ padding: 4px 14px;
+ border-radius: 8px 8px 8px 8px;
+ -moz-border-radius: 8px 8px 8px 8px;
+ -webkit-border-top-left-radius: 8px;
+ -webkit-border-top-right-radius: 8px;
+ -webkit-border-bottom-right-radius: 8px;
+ -webkit-border-bottom-left-radius: 8px;
+ color: #2E2925;
+ cursor: pointer;
+}
+.collapsible-menu #header #block-menu-menu-social-links {
+ position: relative;
+ right: 0;
+ top: 70px;
+}
+.collapsible-menu #header #block-search-form .block-inner .container-inline,
+.collapsible-menu #header #search-block-form .block-inner .container-inline {
+ right: 0;
+ top: 75px;
+}
+#menu-tray {
+ width: 100%;
+ min-height: 20px;
+ position: absolute;
+ z-index: 1000;
+ top: 0;
+ background: #ecedee;
+ color: #fff;
+ opacity: 0;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
+ box-shadow: 0 8px 7px 1px rgba(0, 0, 0, 0.45);
+}
+#menu-tray .initially-hidden {
+ opacity: 0;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
+}
+#menu-tray.menu-tray-transition {
+ -webkit-transition: opacity 0.25s ease-in-out;
+ -moz-transition: opacity 0.25s ease-in-out;
+ transition: opacity 0.25s ease-in-out;
+ pointer-events: auto;
+ opacity: 1;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
+}
+#menu-tray .menu {
+ padding: 20px;
+}
+#menu-tray.docked {
+ pointer-events: none;
+ opacity: 0;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
+}
+#menu-tray a {
+ color: #fff;
+ word-wrap: break-word;
+}
+#menu-tray .menu .menu-level-2 {
+ display: inline-block;
+ vertical-align: top;
+ width: 25%;
+ margin: 0 0 0.25em 0;
+ padding: 0;
+}
+@media screen and (min-width: 0) and (max-width: 500px) {
+ #menu-tray .menu .menu-level-2 {
+ width: 100%;
+ }
+}
+@media screen and (min-width: 500px) and (max-width: 700px) {
+ #menu-tray .menu .menu-level-2 {
+ width: 50%;
+ }
+}
+@media screen and (min-width: 700px) and (max-width: 900px) {
+ #menu-tray .menu .menu-level-2 {
+ width: 33.33%;
+ }
+}
+#menu-tray .menu .menu-level-2 > a {
+ display: block;
+ background: url(/sites/all/themes/opnfv/images/optimized/nav-arrow.png) no-repeat right center #2E2925;
+ border-left: 5px solid #086960;
+ width: 99%;
+ margin: 0 auto 0.25em;
+ padding: 7px;
+}
+#menu-tray .menu .menu-level-2 .link-order-1 {
+ border-left: 5px solid #a4d384;
+}
+#menu-tray .menu .menu-level-2 .link-order-2 {
+ border-left: 5px solid #00adbb;
+}
+#menu-tray .menu .menu-level-2 .menu {
+ font-size: 12px;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+#menu-tray .menu .menu-level-2 .menu li {
+ list-style: none;
+}
+#menu-tray .menu .menu-level-2 .menu a {
+ color: #000;
+ border-left: none;
+}
+#footer .menu-block-wrapper {
+ background: none;
+}
+#footer .menu-block-wrapper .menu li {
+ list-style: none;
+ margin: 0;
+}
+#footer .menu-block-wrapper .menu li a {
+ word-wrap: break-word;
+}
+#footer .menu-block-wrapper .menu .menu-level-1 {
+ display: inline-block;
+ vertical-align: top;
+ width: 25%;
+ margin: 0 0 3em 0;
+ padding: 0;
+}
+@media screen and (min-width: 0) and (max-width: 500px) {
+ #footer .menu-block-wrapper .menu .menu-level-1 {
+ width: 100%;
+ }
+}
+@media screen and (min-width: 500px) and (max-width: 700px) {
+ #footer .menu-block-wrapper .menu .menu-level-1 {
+ width: 50%;
+ }
+}
+@media screen and (min-width: 700px) and (max-width: 900px) {
+ #footer .menu-block-wrapper .menu .menu-level-1 {
+ width: 33.33%;
+ }
+}
+#footer .menu-block-wrapper .menu .menu-level-1 > a {
+ display: block;
+ width: 95%;
+ margin: 0 auto 0.25em;
+ padding: 7px;
+ text-transform: uppercase;
+ background: url(/sites/all/themes/opnfv/images/optimized/nav-arrow.png) no-repeat right center #0095a2;
+ border-left: 5px solid #007e88;
+ color: #fff;
+}
+#footer .menu-block-wrapper .menu .menu-level-2 {
+ width: 85%;
+ margin: 0 auto;
+ font-size: 15px;
+ list-style: square;
+ color: #009fac;
+}
+#footer .menu-block-wrapper .menu .menu-level-2 a {
+ color: #fff;
+ display: inline-block;
+ padding-bottom: 7px;
+ word-wrap: break-word;
+}
+#footer .menu-block-wrapper .menu .menu-level-2 > .menu {
+ border-bottom: 1px solid #008792;
+ margin-bottom: 1.2em;
+}
+#footer .menu-block-wrapper .menu .menu-level-3 {
+ display: block;
+ width: 100%;
+ padding-left: 1.6em;
+ border-top: 1px solid #009fac;
+ background: #00a8b6;
+}
+#footer .menu-block-wrapper .menu .menu-level-3 a {
+ font-size: 0.75em;
+}
+#block-menu_block-1 ul ul,
+#block-menu_block-1 .menu-level-2,
+#footer #block-menu_block-3 .block-title {
+ display: none;
+}
+#block-menu-menu-social-links li {
+ float: left;
+ list-style: none;
+}
+.view-blogs .view-header {
+ float: right;
+}
+.view-blogs .view-header a {
+ text-decoration: none;
+}
+.view-blogs .view-header img {
+ vertical-align: text-top;
+}
+body {
+ background: #fff;
+}
+#content {
+ padding: 0 20px;
+ margin-bottom: 2em;
+}
+.breadcrumb a {
+ text-transform: uppercase;
+ font-size: 0.8em;
+ padding: 0 0.35em;
+}
+.breadcrumb a:first-child {
+ padding-left: 0;
+}
+.blog_usernames_blog a,
+.node-readmore a {
+ color: #A3D783;
+ text-transform: uppercase;
+}
+#page {
+ margin-bottom: 0;
+ padding-bottom: 0;
+}
+.collaborative-projects {
+ background-image: -webkit-linear-gradient(right center, #D0D1D1 0, #E6E6E6 69%);
+ background-image: linear-gradient(to left, #D0D1D1 0, #E6E6E6 69%);
+ background-color: #D0D1D1;
+ min-height: 30px;
+}
+.collaborative-projects .gray-diagonal {
+ min-height: 30px;
+ width: 100%;
+ background: url(/opnfv-dashboard/media/diagonal-white.png) transparent repeat scroll top left;
+}
+.collaborative-projects #collaborative-projects-logo {
+ margin-top: 10px;
+ height: 14px;
+ background: url(/opnfv-dashboard/media/collaborative-projects-logo.png) no-repeat scroll 10px center transparent;
+ width: 100%;
+ max-width: 400px;
+ float: left;
+ text-indent: -9000px;
+}
+@media screen and (min-width: 0) and (max-width: 500px) {
+ .collaborative-projects #collaborative-projects-logo {
+ width: 95%;
+ position: relative;
+ left: 8px;
+ background-position: 0 center;
+ -webkit-background-size: contain;
+ -moz-background-size: contain;
+ -o-background-size: contain;
+ background-size: contain;
+ }
+}
+.collaborative-projects #footer-copyright {
+ padding: 16px 0px 16px 20px;
+ font-size: 11px;
+ line-height: 16px;
+ font-weight: 300;
+}
+.collaborative-projects #footer-copyright p {
+ margin: 0;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
+}
+.collaborative-projects #footer-copyright a {
+ text-decoration: underline;
+ color: #393939;
+}
+.collaborative-projects #footer-copyright a:hover {
+ text-decoration: none;
+ color: #0099EE;
+}
+.page-page-not-found #content {
+ margin: 3em 0 5em;
+ text-align: center;
+}
+.page-page-not-found #content p {
+ line-height: 2em;
+}
+#block-views-members-block_2 .views-row,
+#block-views-members-block_5 .views-row,
+#block-views-members-block_6 .views-row {
+ border-bottom: 1px solid #ccc;
+ margin-bottom: 20px;
+ padding-bottom: 20px;
+}
+#block-views-members-block_2 .member-info,
+#block-views-members-block_5 .member-info,
+#block-views-members-block_6 .member-info {
+ margin-bottom: 20px;
+ position: relative;
+}
+#block-views-members-block_2 .member-info:after,
+#block-views-members-block_5 .member-info:after,
+#block-views-members-block_6 .member-info:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+#block-views-members-block_2 .member-info .column-last,
+#block-views-members-block_5 .member-info .column-last,
+#block-views-members-block_6 .member-info .column-last {
+ bottom: -3px;
+ left: 0;
+ padding-left: 120px;
+ position: absolute;
+ width: 100%;
+}
+#block-views-members-block_2 .member-info .column-last *,
+#block-views-members-block_5 .member-info .column-last *,
+#block-views-members-block_6 .member-info .column-last * {
+ line-height: 120%;
+ margin: 0;
+}
+#block-views-members-block_2 .member-info img,
+#block-views-members-block_5 .member-info img,
+#block-views-members-block_6 .member-info img {
+ display: block;
+}
+#block-views-members-block_2 .member-info h3,
+#block-views-members-block_5 .member-info h3,
+#block-views-members-block_6 .member-info h3 {
+ font-size: 1em;
+}
+#block-views-members-block_2 .member-info .board-member-title,
+#block-views-members-block_5 .member-info .board-member-title,
+#block-views-members-block_6 .member-info .board-member-title {
+ font-weight: normal;
+ padding-left: 0;
+}
+#block-views-members-block_2 p:last-child,
+#block-views-members-block_5 p:last-child,
+#block-views-members-block_6 p:last-child {
+ margin-bottom: 0;
+}
+#block-views-members-block_2 .member-website,
+#block-views-members-block_5 .member-website,
+#block-views-members-block_6 .member-website {
+ margin-top: 10px;
+}
+.page-news-resources .views-row,
+#block-views-resources-block_1 .views-row {
+ margin-bottom: 10px;
+}
+.page-news-resources .views-row:after,
+#block-views-resources-block_1 .views-row:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+.page-news-resources .views-row .views-field-nothing,
+#block-views-resources-block_1 .views-row .views-field-nothing {
+ float: left;
+ width: 80%;
+}
+.page-news-resources .views-row .views-field-field-thumbnail,
+#block-views-resources-block_1 .views-row .views-field-field-thumbnail {
+ float: right;
+ margin: 5px 0 20px 0;
+ width: 15%;
+}
+.page-news-resources .views-row .views-field-field-thumbnail img,
+#block-views-resources-block_1 .views-row .views-field-field-thumbnail img {
+ display: block;
+ height: auto;
+ max-width: 100%;
+}
+.page-news-resources .view-empty,
+#block-views-resources-block_1 .view-empty {
+ margin-bottom: 1em;
+ padding: 1em;
+}
+.page-news-resources .more-link,
+#block-views-resources-block_1 .more-link {
+ bottom: 2em;
+ font-size: 0.8em;
+ position: absolute;
+ text-align: center;
+ text-transform: uppercase;
+ width: 100%;
+}
+#block-views-collateral-block_1 .views-row {
+ margin-bottom: 10px;
+}
+#block-views-collateral-block_1 .view-empty {
+ margin-bottom: 1em;
+ padding: 1em;
+}
+#block-views-collateral-block_1 .more-link {
+ bottom: 2em;
+ font-size: 0.8em;
+ position: absolute;
+ text-align: center;
+ text-transform: uppercase;
+ width: 100%;
+}
+.not-front #block-views-news_and_announcements-block,
+.not-front #block-views-news_and_announcements-block_1,
+.not-front #block-twitter_helper-twitter_block_1,
+.not-front #block-views-events-block,
+.not-front #block-views-blogs-block,
+.not-front #block-views-faq-faq_recent,
+.not-front #block-views-resources-block_1,
+.not-front #block-views-collateral-block_1,
+.not-front #block-views-videos-block {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 32.20339%;
+ height: 450px;
+ margin-bottom: 2em;
+ margin-right: 5px;
+ background: #ecedee;
+ z-index: 1;
+}
+.not-front #block-views-news_and_announcements-block:last-child,
+.not-front #block-views-news_and_announcements-block_1:last-child,
+.not-front #block-twitter_helper-twitter_block_1:last-child,
+.not-front #block-views-events-block:last-child,
+.not-front #block-views-blogs-block:last-child,
+.not-front #block-views-faq-faq_recent:last-child,
+.not-front #block-views-resources-block_1:last-child,
+.not-front #block-views-collateral-block_1:last-child,
+.not-front #block-views-videos-block:last-child {
+ margin-right: 0;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+ .not-front #block-views-news_and_announcements-block,
+ .not-front #block-views-news_and_announcements-block_1,
+ .not-front #block-twitter_helper-twitter_block_1,
+ .not-front #block-views-events-block,
+ .not-front #block-views-blogs-block,
+ .not-front #block-views-faq-faq_recent,
+ .not-front #block-views-resources-block_1,
+ .not-front #block-views-collateral-block_1,
+ .not-front #block-views-videos-block {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 100%;
+ height: auto;
+ min-height: 300px;
+ }
+ .not-front #block-views-news_and_announcements-block:last-child,
+ .not-front #block-views-news_and_announcements-block_1:last-child,
+ .not-front #block-twitter_helper-twitter_block_1:last-child,
+ .not-front #block-views-events-block:last-child,
+ .not-front #block-views-blogs-block:last-child,
+ .not-front #block-views-faq-faq_recent:last-child,
+ .not-front #block-views-resources-block_1:last-child,
+ .not-front #block-views-collateral-block_1:last-child,
+ .not-front #block-views-videos-block:last-child {
+ margin-right: 0;
+ }
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper,
+.not-front #block-views-events-block.block-twitter-helper,
+.not-front #block-views-blogs-block.block-twitter-helper,
+.not-front #block-views-faq-faq_recent.block-twitter-helper,
+.not-front #block-views-resources-block_1.block-twitter-helper,
+.not-front #block-views-collateral-block_1.block-twitter-helper,
+.not-front #block-views-videos-block.block-twitter-helper {
+ margin-right: 0;
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper .all-tweets,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper .all-tweets,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper .all-tweets,
+.not-front #block-views-events-block.block-twitter-helper .all-tweets,
+.not-front #block-views-blogs-block.block-twitter-helper .all-tweets,
+.not-front #block-views-faq-faq_recent.block-twitter-helper .all-tweets,
+.not-front #block-views-resources-block_1.block-twitter-helper .all-tweets,
+.not-front #block-views-collateral-block_1.block-twitter-helper .all-tweets,
+.not-front #block-views-videos-block.block-twitter-helper .all-tweets {
+ position: absolute;
+ bottom: 2em;
+ left: 0;
+ width: 100%;
+ text-align: center;
+ font-size: 0.8em;
+ text-transform: uppercase;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+ .not-front #block-views-news_and_announcements-block.block-twitter-helper .all-tweets,
+ .not-front #block-views-news_and_announcements-block_1.block-twitter-helper .all-tweets,
+ .not-front #block-twitter_helper-twitter_block_1.block-twitter-helper .all-tweets,
+ .not-front #block-views-events-block.block-twitter-helper .all-tweets,
+ .not-front #block-views-blogs-block.block-twitter-helper .all-tweets,
+ .not-front #block-views-faq-faq_recent.block-twitter-helper .all-tweets,
+ .not-front #block-views-resources-block_1.block-twitter-helper .all-tweets,
+ .not-front #block-views-collateral-block_1.block-twitter-helper .all-tweets,
+ .not-front #block-views-videos-block.block-twitter-helper .all-tweets {
+ bottom: 0.5em;
+ }
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper .block-title,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper .block-title,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper .block-title,
+.not-front #block-views-events-block.block-twitter-helper .block-title,
+.not-front #block-views-blogs-block.block-twitter-helper .block-title,
+.not-front #block-views-faq-faq_recent.block-twitter-helper .block-title,
+.not-front #block-views-resources-block_1.block-twitter-helper .block-title,
+.not-front #block-views-collateral-block_1.block-twitter-helper .block-title,
+.not-front #block-views-videos-block.block-twitter-helper .block-title {
+ background: #71c48f;
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper ul,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper ul,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper ul,
+.not-front #block-views-events-block.block-twitter-helper ul,
+.not-front #block-views-blogs-block.block-twitter-helper ul,
+.not-front #block-views-faq-faq_recent.block-twitter-helper ul,
+.not-front #block-views-resources-block_1.block-twitter-helper ul,
+.not-front #block-views-collateral-block_1.block-twitter-helper ul,
+.not-front #block-views-videos-block.block-twitter-helper ul {
+ list-style: none;
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper li,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper li,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper li,
+.not-front #block-views-events-block.block-twitter-helper li,
+.not-front #block-views-blogs-block.block-twitter-helper li,
+.not-front #block-views-faq-faq_recent.block-twitter-helper li,
+.not-front #block-views-resources-block_1.block-twitter-helper li,
+.not-front #block-views-collateral-block_1.block-twitter-helper li,
+.not-front #block-views-videos-block.block-twitter-helper li {
+ margin-bottom: 1em;
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper li img,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper li img,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper li img,
+.not-front #block-views-events-block.block-twitter-helper li img,
+.not-front #block-views-blogs-block.block-twitter-helper li img,
+.not-front #block-views-faq-faq_recent.block-twitter-helper li img,
+.not-front #block-views-resources-block_1.block-twitter-helper li img,
+.not-front #block-views-collateral-block_1.block-twitter-helper li img,
+.not-front #block-views-videos-block.block-twitter-helper li img {
+ float: left;
+ margin-right: 1em;
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper li .tweet_time,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper li .tweet_time,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper li .tweet_time,
+.not-front #block-views-events-block.block-twitter-helper li .tweet_time,
+.not-front #block-views-blogs-block.block-twitter-helper li .tweet_time,
+.not-front #block-views-faq-faq_recent.block-twitter-helper li .tweet_time,
+.not-front #block-views-resources-block_1.block-twitter-helper li .tweet_time,
+.not-front #block-views-collateral-block_1.block-twitter-helper li .tweet_time,
+.not-front #block-views-videos-block.block-twitter-helper li .tweet_time {
+ display: block;
+ font-size: 0.8em;
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper li .tweet_text,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper li .tweet_text,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper li .tweet_text,
+.not-front #block-views-events-block.block-twitter-helper li .tweet_text,
+.not-front #block-views-blogs-block.block-twitter-helper li .tweet_text,
+.not-front #block-views-faq-faq_recent.block-twitter-helper li .tweet_text,
+.not-front #block-views-resources-block_1.block-twitter-helper li .tweet_text,
+.not-front #block-views-collateral-block_1.block-twitter-helper li .tweet_text,
+.not-front #block-views-videos-block.block-twitter-helper li .tweet_text {
+ font-size: 10px;
+}
+@media screen and (min-width: 900px) {
+ .not-front #block-views-news_and_announcements-block.block-twitter-helper li .tweet_text,
+ .not-front #block-views-news_and_announcements-block_1.block-twitter-helper li .tweet_text,
+ .not-front #block-twitter_helper-twitter_block_1.block-twitter-helper li .tweet_text,
+ .not-front #block-views-events-block.block-twitter-helper li .tweet_text,
+ .not-front #block-views-blogs-block.block-twitter-helper li .tweet_text,
+ .not-front #block-views-faq-faq_recent.block-twitter-helper li .tweet_text,
+ .not-front #block-views-resources-block_1.block-twitter-helper li .tweet_text,
+ .not-front #block-views-collateral-block_1.block-twitter-helper li .tweet_text,
+ .not-front #block-views-videos-block.block-twitter-helper li .tweet_text {
+ font-size: 0.9em;
+ }
+}
+.not-front #block-views-news_and_announcements-block.block-even .block-title,
+.not-front #block-views-news_and_announcements-block_1.block-even .block-title,
+.not-front #block-twitter_helper-twitter_block_1.block-even .block-title,
+.not-front #block-views-events-block.block-even .block-title,
+.not-front #block-views-blogs-block.block-even .block-title,
+.not-front #block-views-faq-faq_recent.block-even .block-title,
+.not-front #block-views-resources-block_1.block-even .block-title,
+.not-front #block-views-collateral-block_1.block-even .block-title,
+.not-front #block-views-videos-block.block-even .block-title {
+ background: #177870;
+}
+.not-front #block-views-news_and_announcements-block .block-title,
+.not-front #block-views-news_and_announcements-block_1 .block-title,
+.not-front #block-twitter_helper-twitter_block_1 .block-title,
+.not-front #block-views-events-block .block-title,
+.not-front #block-views-blogs-block .block-title,
+.not-front #block-views-faq-faq_recent .block-title,
+.not-front #block-views-resources-block_1 .block-title,
+.not-front #block-views-collateral-block_1 .block-title,
+.not-front #block-views-videos-block .block-title {
+ background: #00ADBB;
+ color: #fff;
+ font-size: 1.25em;
+ padding: .75em .5em;
+ text-align: center;
+ text-transform: uppercase;
+}
+.not-front #block-views-news_and_announcements-block .block-title a,
+.not-front #block-views-news_and_announcements-block_1 .block-title a,
+.not-front #block-twitter_helper-twitter_block_1 .block-title a,
+.not-front #block-views-events-block .block-title a,
+.not-front #block-views-blogs-block .block-title a,
+.not-front #block-views-faq-faq_recent .block-title a,
+.not-front #block-views-resources-block_1 .block-title a,
+.not-front #block-views-collateral-block_1 .block-title a,
+.not-front #block-views-videos-block .block-title a {
+ color: #fff;
+}
+.not-front #block-views-news_and_announcements-block .view-content,
+.not-front #block-views-news_and_announcements-block_1 .view-content,
+.not-front #block-twitter_helper-twitter_block_1 .view-content,
+.not-front #block-views-events-block .view-content,
+.not-front #block-views-blogs-block .view-content,
+.not-front #block-views-faq-faq_recent .view-content,
+.not-front #block-views-resources-block_1 .view-content,
+.not-front #block-views-collateral-block_1 .view-content,
+.not-front #block-views-videos-block .view-content {
+ margin-bottom: 1em;
+ padding: 1em;
+}
+.not-front #block-views-news_and_announcements-block .view-footer,
+.not-front #block-views-news_and_announcements-block_1 .view-footer,
+.not-front #block-twitter_helper-twitter_block_1 .view-footer,
+.not-front #block-views-events-block .view-footer,
+.not-front #block-views-blogs-block .view-footer,
+.not-front #block-views-faq-faq_recent .view-footer,
+.not-front #block-views-resources-block_1 .view-footer,
+.not-front #block-views-collateral-block_1 .view-footer,
+.not-front #block-views-videos-block .view-footer {
+ text-transform: uppercase;
+ font-size: 0.8em;
+ position: absolute;
+ bottom: 2em;
+ width: 100%;
+ text-align: center;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+ .not-front #block-views-news_and_announcements-block .view-footer,
+ .not-front #block-views-news_and_announcements-block_1 .view-footer,
+ .not-front #block-twitter_helper-twitter_block_1 .view-footer,
+ .not-front #block-views-events-block .view-footer,
+ .not-front #block-views-blogs-block .view-footer,
+ .not-front #block-views-faq-faq_recent .view-footer,
+ .not-front #block-views-resources-block_1 .view-footer,
+ .not-front #block-views-collateral-block_1 .view-footer,
+ .not-front #block-views-videos-block .view-footer {
+ bottom: 0.5em;
+ }
+}
+.not-front .view-blogs .block-row,
+.not-front .view-events .block-row,
+.not-front .view-news-and-announcements .block-row,
+.not-front .view-videos .block-row {
+ margin-bottom: 1.5em;
+}
+.not-front .view-blogs .views-field,
+.not-front .view-events .views-field,
+.not-front .view-news-and-announcements .views-field,
+.not-front .view-videos .views-field {
+ margin-bottom: 0.25em;
+}
+.not-front .view-blogs .views-field-type,
+.not-front .view-blogs .views-field-field-event-address,
+.not-front .view-events .views-field-type,
+.not-front .view-events .views-field-field-event-address,
+.not-front .view-news-and-announcements .views-field-type,
+.not-front .view-news-and-announcements .views-field-field-event-address,
+.not-front .view-videos .views-field-type,
+.not-front .view-videos .views-field-field-event-address {
+ font-style: italic;
+ font-size: 0.95em;
+}
+.not-front .view-blogs .views-field-title,
+.not-front .view-events .views-field-title,
+.not-front .view-news-and-announcements .views-field-title,
+.not-front .view-videos .views-field-title {
+ font-size: 11px;
+}
+@media screen and (min-width: 900px) {
+ .not-front .view-blogs .views-field-title,
+ .not-front .view-events .views-field-title,
+ .not-front .view-news-and-announcements .views-field-title,
+ .not-front .view-videos .views-field-title {
+ font-size: 1.1em;
+ }
+}
+.not-front .view-blogs .views-field-created,
+.not-front .view-events .views-field-created,
+.not-front .view-news-and-announcements .views-field-created,
+.not-front .view-videos .views-field-created {
+ font-style: italic;
+ font-size: 0.8em;
+}
+.not-front #block-views-faq-faq_recent .more-link {
+ position: absolute;
+ width: 100%;
+ text-align: center;
+ bottom: 30px;
+}
+.not-front .view-videos .views-row {
+ display: block;
+ height: 150px;
+ margin-bottom: 1em;
+}
+.not-front .view-videos .view-empty {
+ padding: 1em;
+}
+.not-front .view-videos .views-field-nothing {
+ position: relative;
+ float: left;
+ margin-right: 10px;
+}
+.not-front .view-videos .views-field-nothing a {
+ text-transform: uppercase;
+}
+.not-front .view-videos .views-field-body {
+ margin-top: 20px;
+ font-size: 10px;
+}
+@media screen and (min-width: 900px) {
+ .not-front .view-videos .views-field-body {
+ font-size: 0.9em;
+ }
+}
+.front .title {
+ display: none;
+}
+.front #content {
+ padding-top: 20px;
+ max-width: 68em;
+ margin-left: auto;
+ margin-right: auto;
+}
+.front #content:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+.front .custom_home_block {
+ background: transparent none repeat scroll 0 0 !important;
+ clear: both !important;
+ color: #000 !important;
+ height: auto !important;
+ width: 100% !important;
+ font-size: 0.8em !important;
+ margin-top: 0px !important;
+ margin-bottom: 5px !important;
+}
+.front .custom_home_block .content {
+ padding: 0px !important;
+}
+.front .custom_home_block .content p {
+ margin: 0px;
+}
+.front .custom_home_block img {
+ width: 100%;
+}
+.front .block-views,
+.front .block-block,
+.front .block-twitter-helper {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 32.20339%;
+ height: 500px;
+ margin-bottom: 2em;
+ background: #ecedee;
+}
+.front .block-views:last-child,
+.front .block-block:last-child,
+.front .block-twitter-helper:last-child {
+ margin-right: 0;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+ .front .block-views,
+ .front .block-block,
+ .front .block-twitter-helper {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 100%;
+ height: auto;
+ }
+ .front .block-views:last-child,
+ .front .block-block:last-child,
+ .front .block-twitter-helper:last-child {
+ margin-right: 0;
+ }
+}
+.front .block-views.block-twitter-helper,
+.front .block-block.block-twitter-helper,
+.front .block-twitter-helper.block-twitter-helper {
+ margin-right: 0;
+}
+.front .block-views.block-twitter-helper .all-tweets,
+.front .block-block.block-twitter-helper .all-tweets,
+.front .block-twitter-helper.block-twitter-helper .all-tweets {
+ position: absolute;
+ bottom: 2em;
+ left: 0;
+ width: 100%;
+ text-align: center;
+ font-size: 0.8em;
+ text-transform: uppercase;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+ .front .block-views.block-twitter-helper .all-tweets,
+ .front .block-block.block-twitter-helper .all-tweets,
+ .front .block-twitter-helper.block-twitter-helper .all-tweets {
+ bottom: 0.5em;
+ }
+}
+.front .block-views.block-twitter-helper .block-title,
+.front .block-block.block-twitter-helper .block-title,
+.front .block-twitter-helper.block-twitter-helper .block-title {
+ background: #71c48f;
+}
+.front .block-views.block-twitter-helper ul,
+.front .block-block.block-twitter-helper ul,
+.front .block-twitter-helper.block-twitter-helper ul {
+ list-style: none;
+}
+.front .block-views.block-twitter-helper li,
+.front .block-block.block-twitter-helper li,
+.front .block-twitter-helper.block-twitter-helper li {
+ margin-bottom: 1em;
+}
+.front .block-views.block-twitter-helper li img,
+.front .block-block.block-twitter-helper li img,
+.front .block-twitter-helper.block-twitter-helper li img {
+ float: left;
+ margin-right: 1em;
+}
+.front .block-views.block-twitter-helper li .tweet_time,
+.front .block-block.block-twitter-helper li .tweet_time,
+.front .block-twitter-helper.block-twitter-helper li .tweet_time {
+ display: block;
+ font-size: 0.8em;
+}
+.front .block-views.block-twitter-helper li .tweet_text,
+.front .block-block.block-twitter-helper li .tweet_text,
+.front .block-twitter-helper.block-twitter-helper li .tweet_text {
+ font-size: 10px;
+}
+@media screen and (min-width: 900px) {
+ .front .block-views.block-twitter-helper li .tweet_text,
+ .front .block-block.block-twitter-helper li .tweet_text,
+ .front .block-twitter-helper.block-twitter-helper li .tweet_text {
+ font-size: 0.9em;
+ }
+}
+.front .block-views.block-even .block-title,
+.front .block-block.block-even .block-title,
+.front .block-twitter-helper.block-even .block-title {
+ background: #177870;
+}
+.front .block-views .block-title,
+.front .block-block .block-title,
+.front .block-twitter-helper .block-title {
+ background: #00ADBB;
+ color: #fff;
+ font-size: 1.25em;
+ padding: .75em .5em;
+ text-align: center;
+ text-transform: uppercase;
+}
+.front .block-views .block-title a,
+.front .block-block .block-title a,
+.front .block-twitter-helper .block-title a {
+ color: #fff;
+}
+.front .block-views .content,
+.front .block-block .content,
+.front .block-twitter-helper .content {
+ padding: 1em;
+}
+.front .block-views .view-content,
+.front .block-block .view-content,
+.front .block-twitter-helper .view-content {
+ margin-bottom: 1em;
+}
+.front .block-views .view-footer,
+.front .block-block .view-footer,
+.front .block-twitter-helper .view-footer {
+ text-transform: uppercase;
+ font-size: 0.8em;
+ position: absolute;
+ bottom: 2em;
+ width: 100%;
+ text-align: center;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+ .front .block-views .view-footer,
+ .front .block-block .view-footer,
+ .front .block-twitter-helper .view-footer {
+ bottom: 0.5em;
+ }
+}
+.front #content-area {
+ display: inline-flex;
+ height: auto;
+ flex-flow: row wrap;
+}
+.front #block-block-1,
+.front #block-block-2,
+.front #block-block-3 {
+ background: none;
+ height: auto;
+ display: inline-flex;
+ margin-bottom: 0;
+}
+@media screen and (min-width: 900px) {
+ .front #block-block-1,
+ .front #block-block-2,
+ .front #block-block-3 {
+ width: 32%;
+ }
+}
+.front #block-block-1 .block-title,
+.front #block-block-2 .block-title,
+.front #block-block-3 .block-title {
+ background: transparent;
+ margin-bottom: 0;
+ padding: 0;
+}
+.front #block-block-1 .block-title-link,
+.front #block-block-2 .block-title-link,
+.front #block-block-3 .block-title-link {
+ background-position: 50% 10px;
+ background-repeat: no-repeat;
+ color: #373A36;
+ display: block;
+ margin: auto;
+ padding-top: 110px;
+ width: 100%;
+}
+.front #block-block-1 .content,
+.front #block-block-2 .content,
+.front #block-block-3 .content {
+ text-align: center;
+}
+.front #block-block-1 .block-title-link {
+ background-image: url(/sites/all/themes/opnfv/images/optimized/OPNFV_Icon_About.png);
+}
+.front #block-block-2 .block-title-link {
+ background-image: url(/sites/all/themes/opnfv/images/optimized/OPNFV_Icon_GettingStarted.png);
+}
+.front #block-block-3 {
+ margin-right: 0;
+}
+.front #block-block-3 .block-title-link {
+ background-image: url(/sites/all/themes/opnfv/images/optimized/OPNFV_Icon_GetInvolved.png);
+}
+.front #block-system-main {
+ clear: both;
+}
+.front .view-blogs .views-row,
+.front .view-events .views-row {
+ margin-bottom: 1.5em;
+}
+.front .view-blogs .views-field,
+.front .view-events .views-field {
+ margin-bottom: 0.25em;
+}
+.front .view-blogs .views-field-type,
+.front .view-blogs .views-field-field-event-address,
+.front .view-events .views-field-type,
+.front .view-events .views-field-field-event-address {
+ font-style: italic;
+ font-size: 0.95em;
+}
+.front .view-blogs .views-field-title,
+.front .view-events .views-field-title {
+ font-size: 1.1em;
+}
+.front .view-blogs .views-field-created,
+.front .view-events .views-field-created {
+ font-style: italic;
+ font-size: 0.8em;
+}
+.front #block-views-members_by_level-block,
+.front #block-views-members_by_level-block_1,
+.front #block-views-members-slider {
+ height: 100%;
+ background-color: transparent;
+ width: 100%;
+}
+.front .flex-control-nav {
+ display: none;
+}
+.front .views-field-field-member-logo .field-content {
+ line-height: 40px;
+ text-align: center;
+ width: 100%;
+}
+.front .views-field-field-member-logo .field-content img {
+ display: inline-block;
+ height: auto;
+ max-width: 100%;
+ vertical-align: middle;
+ width: auto;
+}
+#highlighted {
+ margin-bottom: -221px;
+}
+#highlighted .block-views {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 100%;
+ height: auto;
+ margin-bottom: 0;
+ background: none;
+}
+#highlighted .block-views:last-child {
+ margin-right: 0;
+}
+.logged-in #highlighted {
+ margin-bottom: -216px;
+}
+.flexslider {
+ background: transparent;
+ border: none;
+ border-radius: none;
+ box-shadow: none;
+}
+.flexslider .flex-direction-nav a:before {
+ color: #A3D783;
+}
+.flexslider .flex-direction-nav a:hover {
+ text-decoration: none;
+}
+.flexslider .slides {
+ overflow: hidden;
+}
+.flexslider .slides img {
+ height: auto;
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 100%;
+}
+.flexslider .views-field-nothing {
+ display: none;
+}
+.view-front-page-slideshow {
+ max-height: 180px;
+ overflow: hidden;
+}
+@media screen and (min-width: 981px) {
+ .view-front-page-slideshow .flexslider .slides img {
+ height: 180px;
+ }
+}
+a.frontpage-slider__video-link,
+a.frontpage-slider__video-link:visited {
+ display: block;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ overflow: hidden;
+ text-indent: -999em;
+ top: 0;
+ width: 100%;
+ z-index: 1;
+}
+.view-members-by-level .view-content {
+ text-align: center;
+}
+.view-members-by-level .view-content h3 {
+ text-align: left;
+}
+.view-members-by-level .field-content,
+.view-members-by-level .views-field-field-member-logo,
+.view-members-by-level .views-row {
+ display: inline-block;
+}
+.view-members-by-level .flexslider {
+ background-color: transparent;
+}
+.view-members-by-level img {
+ margin-right: 10px;
+}
+@media screen and (min-width: 0) and (max-width: 670px) {
+ .flexslider .views-field-nothing {
+ position: static;
+ opacity: 0.8;
+ }
+ .touch .flexslider .flex-direction-nav {
+ display: none;
+ }
+}
+.view-events .views-row {
+ margin-bottom: 2em;
+}
+.view-events .views-field {
+ margin-bottom: 0.5em;
+}
+.view-events .learn-more {
+ margin: 0.5em 0;
+}
+.view-events .views-field-title {
+ font-size: 1.25em;
+}
+.view-events .views-field-created {
+ font-style: italic;
+ font-size: 0.8em;
+}
+.view-events .views-field-field-event-address {
+ font-style: italic;
+}
+.node-event img {
+ max-width: 100%;
+ height: auto;
+}
+.node-event .field {
+ margin-bottom: 0.5em;
+}
+.node-event .field-label {
+ margin-right: 0.25em;
+ text-transform: uppercase;
+ font-size: 0.8em;
+}
+.node-event .field-name-field-event-image {
+ margin-bottom: 2em;
+ border-bottom: 1px solid #ecedee;
+}
+.node-event .field-name-body {
+ margin: 2em 0;
+ padding-top: 3em;
+ border-top: 1px solid #ecedee;
+}
+.node-event .field-name-body .field-label {
+ margin-bottom: 1em;
+}
+.view-main-menu-links {
+ max-width: 68em;
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 2em;
+ border-top: 1px solid #ecedee;
+ padding-top: 4em;
+}
+.view-main-menu-links:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+.view-main-menu-links ul {
+ margin: 0;
+ padding: 0;
+}
+.view-main-menu-links ul li {
+ margin: 0;
+ padding: 0;
+}
+.view-main-menu-links .views-row {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 49.15254%;
+ margin-bottom: 4em;
+ padding-right: 4em;
+}
+.view-main-menu-links .views-row:last-child {
+ margin-right: 0;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+ .view-main-menu-links .views-row {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 100%;
+ padding-right: 0;
+ }
+ .view-main-menu-links .views-row:last-child {
+ margin-right: 0;
+ }
+}
+.view-main-menu-links .views-row .views-field-title {
+ font-size: 1.5em;
+ margin-bottom: 1em;
+ text-align: center;
+}
+.view-main-menu-links .views-row .views-field-body {
+ margin-bottom: 0.5em;
+}
+.view-main-menu-links .views-row .views-field-description a {
+ display: block;
+ margin-top: 0.5em;
+}
+.view-main-menu-links .views-row.views-row-even {
+ margin-right: 0;
+}
+.view-main-menu-links .views-row.views-row-odd {
+ clear: left;
+}
+body.node-type-landing-page #content-header {
+ left: 0;
+ position: absolute;
+ top: 20px;
+ width: 100%;
+ z-index: 1;
+}
+.node-landing-page {
+ display: block;
+ height: 100%;
+ left: 0;
+ overflow: auto;
+ position: fixed;
+ top: 0;
+ width: 100%;
+}
+.node-landing-page.with-background {
+ background-color: #000;
+ background-position: center bottom;
+ background-repeat: no-repeat;
+ background-size: cover;
+}
+.node-landing-page .content {
+ color: #fff;
+ margin: 50px auto;
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.75);
+}
+.node-landing-page .content:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+@media screen and (min-width: 700px) {
+ .node-landing-page .content {
+ margin-top: 120px;
+ max-width: 68em;
+ }
+}
+.node-landing-page .content a,
+.node-landing-page .content a:visited {
+ color: #00ADBB;
+}
+.node-landing-page .content p {
+ color: #fff;
+}
+.node-landing-page .content h2 {
+ text-align: center;
+}
+.node-landing-page .content h2 a,
+.node-landing-page .content h2 a:visited {
+ color: #fff;
+}
+.node-landing-page .content img {
+ display: inline-block;
+ height: auto;
+ max-width: 100%;
+}
+.node-landing-page .field-name-field-header-first-column,
+.node-landing-page .field-name-field-header-last-column,
+.node-landing-page .field-name-field-content-first-column,
+.node-landing-page .field-name-field-content-last-column {
+ padding: 0 20px;
+}
+@media screen and (min-width: 700px) {
+ .node-landing-page .field-name-field-header-first-column,
+ .node-landing-page .field-name-field-header-last-column,
+ .node-landing-page .field-name-field-content-first-column,
+ .node-landing-page .field-name-field-content-last-column {
+ float: left;
+ width: 49%;
+ }
+}
+@media screen and (min-width: 700px) {
+ .node-landing-page .field-name-field-header-first-column,
+ .node-landing-page .field-name-field-content-last-column {
+ float: right;
+ }
+}
+.node-landing-page .field-name-field-content-first-column {
+ margin-bottom: 40px;
+}
+@media screen and (min-width: 700px) {
+ .node-landing-page .field-name-field-content-first-column {
+ margin-bottom: 0;
+ }
+}
+.page-resources-library .view-resources .views-row {
+ margin-bottom: 2em;
+}
+.page-resources-library .view-resources .opnfv-resources-date {
+ font-style: italic;
+ font-size: 0.8em;
+}
+.page-resources-library .view-resources .opnfv-resources-title {
+ font-size: 1.25em;
+ margin-bottom: 0.5em;
+}
+.page-resources-library .view-resources .views-field-field-thumbnail {
+ float: right;
+ margin: 5px 0 20px 20px;
+}
+.page-resources-library .view-resources .views-field-field-thumbnail img {
+ display: block;
+ height: auto;
+ max-width: 100%;
+}
+.page-blog .view-blogs .views-row,
+.page-news-faq-blog .view-blogs .views-row {
+ margin-bottom: 2em;
+}
+.page-blog .view-blogs .views-field,
+.page-news-faq-blog .view-blogs .views-field {
+ margin-bottom: 0.5em;
+}
+.page-blog .view-blogs .learn-more,
+.page-news-faq-blog .view-blogs .learn-more {
+ margin: 0.5em 0;
+}
+.page-blog .view-blogs .views-field-title,
+.page-news-faq-blog .view-blogs .views-field-title {
+ font-size: 1.25em;
+}
+.page-blog .view-blogs .views-field-created,
+.page-news-faq-blog .view-blogs .views-field-created {
+ font-style: italic;
+ font-size: 0.8em;
+}
+.node-blog {
+ margin-bottom: 3em;
+}
+.node-blog .addtoany_list {
+ display: block;
+ padding-bottom: 20px;
+}
+.node-blog .blog_usernames_blog {
+ display: none;
+}
+#header #header-region {
+ clear: both;
+}
+#header .container {
+ padding: 0px 20px 0;
+}
+#header #logo {
+ float: left;
+ display: block;
+ margin-right: 1.69492%;
+ width: 18.64407%;
+ margin-bottom: 1em;
+}
+#header #logo:last-child {
+ margin-right: 0;
+}
+#header #site-name {
+ margin-top: 20px;
+}
+#header #site-name a {
+ text-indent: -9999px;
+ display: inline-block;
+}
+.banner-top {
+ background-color: #fff;
+ height: 180px;
+}
+@media screen and (min-width: 980px) {
+ .banner-top {
+ background: #00ADBB url(/sites/all/themes/opnfv/images/optimized/banner-bg.png) repeat 50% 50%;
+ height: 180px;
+ }
+}
+.sidebar .block {
+ margin-bottom: 1.5em;
+ background: #ecedee;
+}
+.sidebar .block .content {
+ padding: 0 1em .5em;
+}
+.sidebar .block .view-content {
+ margin-bottom: 1em;
+}
+.sidebar .block .view-footer {
+ text-transform: uppercase;
+ font-size: 0.8em;
+}
+.sidebar .block-title {
+ font-size: 1em;
+ color: #fff;
+ background: #177870;
+ padding: .5em;
+}
+#footer {
+ background: #00ADBB;
+}
+#footer .container {
+ padding: 20px 20px 30px;
+}
+#footer #block-block-4 {
+ display: none;
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block;
+}
+audio,
+canvas,
+video {
+ display: inline-block;
+}
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+[hidden] {
+ display: none;
+}
+html {
+ font-family: sans-serif;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+body {
+ margin: 0;
+}
+a:focus {
+ outline: thin dotted;
+}
+a:active,
+a:hover {
+ outline: 0;
+}
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+b,
+strong {
+ font-weight: bold;
+}
+dfn {
+ font-style: italic;
+}
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0;
+}
+mark {
+ background: #ff0;
+ color: #000;
+}
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, serif;
+ font-size: 1em;
+}
+pre {
+ white-space: pre-wrap;
+}
+q {
+ quotes: "\201C" "\201D" "\2018" "\2019";
+}
+small {
+ font-size: 80%;
+}
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+sup {
+ top: -0.5em;
+}
+sub {
+ bottom: -0.25em;
+}
+img {
+ border: 0;
+}
+svg:not(:root) {
+ overflow: hidden;
+}
+figure {
+ margin: 0;
+}
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+legend {
+ border: 0;
+ padding: 0;
+}
+button,
+input,
+select,
+textarea {
+ font-family: inherit;
+ font-size: 100%;
+ margin: 0;
+}
+button,
+input {
+ line-height: normal;
+}
+button,
+select {
+ text-transform: none;
+}
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button;
+ cursor: pointer;
+}
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box;
+ padding: 0;
+}
+input[type="search"] {
+ -webkit-appearance: textfield;
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box;
+ box-sizing: content-box;
+}
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+textarea {
+ overflow: auto;
+ vertical-align: top;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
diff --git a/tools/infra-dashboard/css/source-sans-pro.css b/tools/infra-dashboard/css/source-sans-pro.css
new file mode 100644
index 00000000..91314f03
--- /dev/null
+++ b/tools/infra-dashboard/css/source-sans-pro.css
@@ -0,0 +1,96 @@
+/* vietnamese */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 200;
+ src: local('Source Sans Pro ExtraLight'), local('SourceSansPro-ExtraLight'), url(../fonts/SourceSansPro-ExtraLight.ttf) format('truetype');
+ unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 200;
+ src: local('Source Sans Pro ExtraLight'), local('SourceSansPro-ExtraLight'), url(../fonts/SourceSansPro-ExtraLight.ttf) format('truetype');
+ unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 200;
+ src: local('Source Sans Pro ExtraLight'), local('SourceSansPro-ExtraLight'), url(../fonts/SourceSansPro-ExtraLight.ttf) format('truetype');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url(../fonts/SourceSansPro-Regular.ttf) format('truetype');
+ unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url(../fonts/SourceSansPro-Regular.ttf) format('truetype');
+ unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url(../fonts/SourceSansPro-Regular.ttf) format('truetype');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 700;
+ src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(../fonts/SourceSansPro-Bold.ttf) format('truetype');
+ unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 700;
+ src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(../fonts/SourceSansPro-Bold.ttf) format('truetype');
+ unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 700;
+ src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(../fonts/SourceSansPro-Bold.ttf) format('truetype');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: italic;
+ font-weight: 400;
+ src: local('Source Sans Pro Italic'), local('SourceSansPro-It'), url(../fonts/SourceSansPro-Italic.ttf) format('truetype');
+ unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: italic;
+ font-weight: 400;
+ src: local('Source Sans Pro Italic'), local('SourceSansPro-It'), url(../fonts/SourceSansPro-Italic.ttf) format('truetype');
+ unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: italic;
+ font-weight: 400;
+ src: local('Source Sans Pro Italic'), local('SourceSansPro-It'), url(../fonts/SourceSansPro-Italic.ttf) format('truetype');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
+}
diff --git a/tools/infra-dashboard/css/template.css b/tools/infra-dashboard/css/template.css
new file mode 100644
index 00000000..688ac04d
--- /dev/null
+++ b/tools/infra-dashboard/css/template.css
@@ -0,0 +1,802 @@
+/*
+*/
+body {
+ background-color: #15151d;
+ color: #313131;
+ font-family: 'Source Sans Pro', sans-serif;
+ font-size: 16px;
+ min-width: 300px;
+}
+h1,
+.h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+ font-weight: bolder;
+ text-transform: uppercase;
+}
+h2,
+.h2 {
+ font-size: 1.5em;
+ margin: 0.83em 0;
+ font-weight: bolder;
+ text-transform: uppercase;
+}
+h3 {
+ font-size: 1.17em;
+ margin: 1em 0;
+ font-weight: bolder;
+ text-transform: uppercase;
+}
+h4 {
+ font-weight: bolder;
+ text-transform: uppercase;
+}
+h5 {
+
+ font-weight: bolder;
+ text-transform: uppercase;
+}
+p {
+ font-size: 16px;
+}
+a {
+ color: #68CDA0;
+ outline: none;
+ text-decoration: none;
+}
+ul,
+ol {
+ padding: 0;
+ margin: 0 0 10px 25px;
+}
+.table-responsive {
+ overflow-x: auto;
+}
+img {
+ max-width: 100%;
+}
+a:focus,
+a:hover {
+ color: #90ef7f;
+ text-decoration: none;
+}
+.btn {
+ text-transform: uppercase;
+ font-weight: bolder;
+}
+.btn-default {
+ background-color: #90ef7f;
+ color: #313131;
+ border: 0;
+ border-radius: 2px;
+}
+.btn-default:hover {
+ background-color: #90ef7f;
+ color: #000000;
+}
+.btn-sm {
+ padding: 13px 18px;
+}
+button,
+.button {
+ font-size: 12px;
+ line-height: 1;
+ display: inline-block;
+ text-transform: uppercase;
+ font-weight: bolder;
+ padding: 16px 18px;
+ background-color: #90ef7f;
+ color: #313131;
+ border: 0;
+ border-radius: 2px;
+ margin: 1px;
+ text-align: center;
+}
+button:hover,
+.button:hover {
+ background-color: #90ef7f;
+ color: #000000;
+}
+.pagination>li>a, .pagination>li>span {
+ color: #FFFFFF;
+ background-color: #0095A2;
+ border: 1px solid silver;
+}
+.pagination>li>a:hover, .pagination>li>span:hover, .pagination>li>a:focus, .pagination>li>span:focus {
+ color: white;
+ background-color: #68CDA0;
+}
+.pagination>li>span:hover, .pagination>li>span:focus {
+ background-color: #0095A2;
+}
+/*
+* Menu
+*/
+#menu {
+ background-color: #007E88;
+ color: #ffffff;
+}
+#menu #menu-second a {
+ font-size: 12px;
+}
+#menu-second .navbar-nav>li>a {
+ padding-bottom: 5px;
+}
+#menu-second .navbar-nav>li>a {
+ padding-top: 10px;
+}
+#menu .navbar-default {
+ background-color: #FFFFFF;
+ color: inherit;
+ border-radius: 0;
+ border: 0;
+ margin-bottom: 0;
+}
+#menu .navbar-brand {
+ height: auto;
+ max-width: 360px;
+ margin: -0 0;
+ padding: 0;
+}
+#menu .navbar-brand img {
+ max-width: 100%;
+ width: 100%;
+ height: auto;
+}
+#menu a {
+ font-size: 20px;
+ font-weight: normal;
+ color: #000000;
+ text-transform: uppercase;
+}
+#menu a:hover {
+ color: #90ef7f;
+}
+#menu .navbar-default .navbar-nav>.active>a,
+#menu .navbar-default .navbar-nav>.active>a:hover,
+#menu .navbar-default .navbar-nav>.active>a:focus {
+ background-color: inherit;
+ color: #68CDA0;
+ font-weight: bolder;
+}
+#menu .navbar-default .navbar-nav>.open>a,
+#menu .navbar-default .navbar-nav>.open>a:hover,
+#menu .navbar-default .navbar-nav>.open>a:focus {
+ background-color: #ECEDEE;
+ color: #000000;
+}
+#menu .dropdown-menu {
+ background-color: #ECEDEE;
+ margin-top: -1px;
+ margin-left: -1px;
+ border-radius: 0;
+}
+#menu .dropdown-menu>li>a:hover,
+#menu .dropdown-menu>li>a:focus {
+ background-color: inherit;
+}
+#menu .dropdown-menu>.active>a,
+#menu .dropdown-menu>.active>a:hover,
+#menu .dropdown-menu>.active>a:focus {
+ background-color: inherit;
+ font-weight: bold;
+}
+#menu .dropdown-menu>li>a {
+ font-size: 16px;
+ text-transform: none;
+}
+
+.highcharts-iframe {
+ border: none;
+}
+
+/*
+*Embedded youtube
+*/
+.video-slot {
+ padding: 2em;
+}
+.video-container-outer {
+ max-width: 800px;
+ margin: 0 auto;
+}
+.video-container {
+ position:relative;
+ padding-bottom:56.25%;
+ height:0;
+ overflow:hidden;
+}
+.video-container iframe {
+ position:absolute;
+ top:0;
+ left:0;
+ width:100%;
+ height:100%;
+}
+/*
+* End: Menu
+*/
+.item-page {
+ max-width: 760px;
+ margin: auto;
+}
+.item-page img {
+ margin-bottom: 30px;
+}
+
+.image-wrapper {
+ background-color: #f9f9f9;
+ padding: 30px;
+ margin-bottom: 30px;
+ text-align: center;
+}
+.image-wrapper img {
+ margin: 0;
+}
+
+.blog img {
+ margin-bottom: 30px;
+}
+
+.article-info {
+ font-size: 14px;
+ color: silver;
+ margin-bottom: 20px;
+}
+
+dl.article-info dt {
+ display: none;
+}
+
+dl.article-info dd {
+ font-size: 14px;
+ display: inline;
+ color: silver;
+ margin-bottom: 20px;
+}
+
+dl.article-info dd:after {
+ content: " \25CF ";
+}
+
+dl.article-info dd:last-child:after {
+ content: none;
+}
+
+hr {
+ width: 100%;
+ border-top: 1px solid silver;
+ display: inline-block;
+}
+
+
+
+/*
+* Component
+#000000
+*/
+#hs-component {
+ background-color: #000000;
+ padding: 30px 0;
+}
+#hs-component .container {
+ background-color: white;
+ padding: 30px;
+}
+/*
+* End: Component
+*/
+/*
+* Footer
+*/
+footer {
+ background-color: #15151d;
+ color: #000000;
+}
+#footer .container {
+ padding: 30px 0;
+}
+#footer a {
+ color: inherit;
+}
+#footer a:hover {
+ color: #90ef7f;
+}
+#footer .socials a {
+ margin-left: 10px;
+}
+/*
+* End: Footer
+*/
+
+/*
+======
+=== Responsive CSS
+======
+*/
+@media screen and (min-width: 768px) {
+ #footer .socials {
+ text-align: right;
+ }
+ #menu .container {
+ padding: 0;
+ }
+ #menu span.toggle-arrow {
+ display: none;
+ }
+ #menu-container {
+ width: 600px;
+ float: right;
+ }
+ #menu .dropdown.active:hover>a,
+ #menu .dropdown.active:hover>a:hover,
+ #menu .dropdown:hover>a {
+ background-color: #ECEDEE;
+ color: #000000;
+ }
+ #menu .dropdown:hover>.dropdown-menu {
+ display: block;
+ }
+ #menu .dropdown-menu {
+ padding: 5px 20px;
+ }
+ #menu .dropdown-menu a {
+ border-bottom: 2px solid #007E88;
+ text-align: right;
+ padding: 10px 0;
+ }
+ #menu .dropdown-menu li:last-child a {
+ border-bottom: 0;
+ }
+ #menu .collapse.navbar-collapse {
+ padding: 0;
+ }
+}
+@media screen and (max-width: 767px) {
+ #menu .container {
+ padding-bottom: 30px;
+ padding-top: 30px;
+ }
+ #menu .navbar-header {
+ position: relative;
+ margin: 0;
+ }
+ #menu .navbar-header .navbar-toggle {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ margin: 0;
+ height: 40px;
+ width: 40px;
+ font-size: 30px;
+ text-align: center;
+ padding: 0;
+ color: #000000;
+ border: 0;
+ border-radius: 0;
+ cursor: pointer;
+ -moz-transform: rotate(-180deg);
+ -moz-transform: rotate(-180deg);
+ -moz-transition: -moz-transform 250ms ease-out 0s;
+ -ms-transform: rotate(-180deg);
+ -o-transform: rotate(-180deg);
+ -o-transition: -o-transform 250ms ease-out 0s;
+ -webkit-transform: rotate(-180deg);
+ -webkit-transition: -webkit-transform 250ms ease-out 0s;
+ transform: rotate(-180deg);
+ transition: transform 250ms ease-out 0s;
+ background-color: #ECEDEE;
+ }
+ #menu .navbar-header .navbar-toggle:hover {
+ color: #90ef7f;
+ }
+ #menu .navbar-header .navbar-toggle.collapsed {
+ -moz-transform: rotate(0deg);
+ -moz-transition: -moz-transform 250ms ease-out 0s;
+ -ms-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ -o-transition: -o-transform 250ms ease-out 0s;
+ -webkit-transform: rotate(0deg);
+ -webkit-transition: -webkit-transform 250ms ease-out 0s;
+ transform: rotate(0deg);
+ transition: transform 250ms ease-out 0s;
+ background-color: inherit;
+ }
+ #menu .navbar-header .navbar-toggle.collapsed:after {
+ font-family: "FontAwesome";
+ content: '\f0c9';
+ }
+ #menu .navbar-header .navbar-toggle:after {
+ font-family: "FontAwesome";
+ content: '\f00d';
+ }
+ #menu .navbar-brand {
+ width: 75%;
+ }
+ #menu .dropdown span.toggle-arrow:after {
+ font-family: "FontAwesome";
+ content: '\f105';
+ }
+ #menu .dropdown.open span.toggle-arrow:after {
+ font-family: "FontAwesome";
+ content: '\f107';
+ }
+ #menu .navbar-collapse {
+ border: 0;
+ background-color: #ECEDEE;
+ text-align: right;
+ margin: 0;
+ }
+ #menu ul.navbar-nav {
+ float: none !important;
+ }
+ #menu .dropdown-menu {
+ text-align: right;
+ }
+ #menu .navbar-nav {
+ margin: 0 -15px;
+ }
+ #menu .nav>li {
+ border-bottom: 2px solid #007E88;
+ }
+ #hs-component {
+ padding: 0;
+ }
+ #footer {
+ text-align: center;
+ }
+}
+
+/*
+==================
+=== About Us page
+==================
+* TODO: find better logic for this
+*/
+#about-us h1,
+#about-us h2 {
+ text-align: center;
+}
+#about-us h2 {
+ margin-top: 2em;
+}
+#about-us #company-container img {
+ width: 100%;
+}
+#about-us .image-container {
+ text-align: center;
+}
+/* Company section */
+
+/* Products section */
+#about-us #products-container div > div {
+ margin-top: 20px;
+ margin-bottom: 10px;
+}
+
+/* Numbers section */
+#about-us #numbers-container {
+ text-align: center;
+}
+#about-us #numbers-container p {
+ padding: 15px;
+ background-color: #ddd;
+ width: 220px;
+ min-height: 130px;
+ margin: 15px;
+ display: inline-block;
+ vertical-align: top;
+ text-align: left;
+ box-sizing: content-box;
+}
+
+/* Employee section */
+#about-us #employees-container {
+ text-align: center;
+}
+#about-us .employee {
+ display: inline-block;
+ width: 210px;
+ margin: 15px;
+ vertical-align: top;
+}
+#about-us .employee .image-container {
+ padding: 5px;
+}
+#about-us .employee .image-container.no-image {
+ height: 210px;
+}
+#about-us .employee .image-container img {
+ max-height: 200px;
+ max-width: 200px;
+ background-color: #000000;
+ margin-bottom: 0;
+}
+#about-us .employee .description p {
+ text-align: left;
+}
+#about-us .employee p.name {
+ font-weight: bold;
+ font-size: 18px;
+ text-align: center;
+}
+
+/* Facts section */
+#about-us #facts-container {
+ word-break: break-word;
+ /*text-align: center;*/
+ width: 50%;
+ float: left;
+}
+
+/* Press section */
+#about-us #press-container {
+ width: 50%;
+ float: left;
+}
+@media screen and (max-width: 767px) {
+ #about-us .employee .image-container.no-image {
+ height: auto;
+ }
+ #about-us #facts-container,
+ #about-us #press-container {
+ width: 100%;
+ float: none;
+ }
+ #about-us #numbers-container p {
+ width: auto;
+ min-height: 0px !important;
+ }
+}
+/*
+=======================
+=== END About Us page
+=======================
+*/
+/*
+=======================
+=== Product Pages
+=======================
+*/
+.text-center {
+ text-align: center;
+}
+.product-buttons .button {
+ max-width: 200px;
+ width: 100%;
+}
+/*
+=======================
+=== END Product Pages
+=======================
+*/
+.datatable th, .datatable td {
+ border: 1px solid silver;
+ padding: 2px 5px;
+}
+.datatable th {
+ text-align: left;
+}
+.intro h4 {
+ clear: both;
+ padding-top: 1em;
+}
+.intro img {
+ margin-bottom: 1em;
+ border: 1px solid silver;
+ max-width: 164px;
+}
+.intro img.float-left {
+ margin-right: 2em;
+}
+.intro img.float-right {
+ margin-left: 2em;
+}
+.float-right {
+ float: right;
+}
+.float-left {
+ float: left;
+}
+table.category {
+ width: 100%;
+}
+table.category .cat-list-row1 {
+ background-color: #000000;
+}
+
+/*
+====
+===
+====
+*/
+
+/* Sidebar */
+#hs-component > div.sidebar-container {
+ padding: 0;
+}
+.nav-sidebar {
+ margin: 0 -15px;
+}
+.nav-sidebar > li {
+ display: block;
+ background-color: #007E88;
+ border-top: 1px solid #0095A2;
+ color: white;
+ font-size: 20px;
+ font-weight: 200;
+ text-transform: uppercase;
+}
+.nav-sidebar > li:last-child {
+ border-bottom: 1px solid #0095A2;
+}
+.nav-sidebar > li.active {
+ background-color: #0095A2;
+ font-weight: bold;
+}
+.nav-sidebar > li > div {
+ position: relative;
+ padding: 10px;
+ padding-right: 40px;
+ cursor: pointer;
+}
+/* Toggle */
+.toggle {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+}
+.active > .toggle::after {
+ font-family: "FontAwesome";
+ content: '\f107';
+}
+.toggle::after {
+ font-family: "FontAwesome";
+ content: '\f105';
+}
+.nav-sidebar > li > ul {
+ /*display: none;*/
+}
+.nav-sidebar > li > ul {
+ /*display: block;*/
+ list-style: none;
+ margin: 0;
+}
+.nav-sidebar > li > ul > li {
+ border-top: 1px solid #007E88;
+ text-transform: none;
+ padding: 10px;
+ line-height: 1;
+}
+.nav-sidebar > li.active > ul > li.active::after {
+ content: '';
+ width: 0;
+ height: 0;
+ position: absolute;
+ right: -10px;
+ border-top: 10px solid transparent;
+ border-bottom: 10px solid transparent;
+ border-left: 10px solid #0095A2;
+ z-index: 1;
+}
+.nav-sidebar > li > ul > li > a {
+ color: white;
+ font-size: 16px;
+ font-weight: 100;
+}
+.nav-sidebar > li.active > ul > li.active > a {
+ color: #68CDA0;
+ font-weight: bold;
+}
+.nav-sidebar > li.active > ul > li > a:focus,
+.nav-sidebar > li.active > ul > li > a:hover {
+ color: #90ef7f;
+}
+.sidebar-wrapper {
+ position: relative;
+}
+.sidebar {
+ z-index: 2;
+ background-color: #007E88;
+}
+.sidebar-eq {
+ z-index: 1;
+ background-color: white;
+}
+.sidebar-fill {
+ position: absolute;
+ height: 100%;
+ background-color: #007E88;
+ left: 0;
+ top: 0;
+ z-index: 0;
+}
+.sidebar-eq-fill {
+ position: absolute;
+ height: 100%;
+ background-color: white;
+ right: 0;
+ top: 0;
+ z-index: 0;
+}
+@media screen and (max-width: 500px) {
+ .intro img {
+ display: none;
+ }
+}
+@media screen and (max-width: 768px) {
+ .sidebar-wrapper {
+ position: relative;
+ overflow: hidden;
+ }
+ .sidebar-wrapper .sidebar {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 9;
+ visibility: hidden;
+ height: 100%;
+ overflow-x: hidden;
+ overflow-y: auto;
+ -webkit-transition: all 0.5s;
+ transition: all 0.5s;
+ }
+ .sidebar-wrapper.toggled .sidebar {
+ visibility: visible;
+ -webkit-transition: -webkit-transform 0.5s;
+ transition: transform 0.5s;
+ }
+ .toggled .nav-sidebar > li.active > ul > li.active::after {
+ display: none;
+ }
+ .sidebar-wrapper .sidebar-eq {
+ position: relative;
+ left: 0;
+ z-index: 10;
+ padding-top: 42px;
+ /*height: 100%;*/
+ -webkit-transition: -webkit-transform 0.5s;
+ transition: transform 0.5s;
+ }
+ .sidebar-wrapper.toggled .sidebar-eq {
+ -webkit-transform: translate3d(75%, 0, 0);
+ transform: translate3d(75%, 0, 0);
+ }
+ .sidebar-wrapper.toggled .sidebar-eq:after {
+ width: 100%;
+ height: 100%;
+ opacity: 1;
+ -webkit-transition: opacity 0.5s;
+ transition: opacity 0.5s;
+ }
+ .sidebar-wrapper .sidebar-eq:after {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 0;
+ height: 0;
+ background: rgba(0,0,0,0.2);
+ content: '';
+ opacity: 0;
+ -webkit-transition: opacity 0.5s, width 0.1s 0.5s, height 0.1s 0.5s;
+ transition: opacity 0.5s, width 0.1s 0.5s, height 0.1s 0.5s;
+ }
+ #sidebar-toggle {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: #007E88;
+ color: white;
+ border-radius: 0;
+ z-index: 100;
+ cursor: pointer;
+ padding: 10px;
+ visibility: visible;
+ }
+ #sidebar-toggle:after {
+ content: "Show Sidebar";
+ }
+ .toggled #sidebar-toggle:after {
+ content: "Hide Sidebar";
+ }
+}
diff --git a/tools/infra-dashboard/css/theme.css b/tools/infra-dashboard/css/theme.css
new file mode 100644
index 00000000..59ae59f0
--- /dev/null
+++ b/tools/infra-dashboard/css/theme.css
@@ -0,0 +1,387 @@
+.container {
+ width: 100%
+}
+#hs-component {
+ background-color: inherit;
+ padding: 0
+}
+#hs-component .container {
+ padding: 0;
+ width: 100%
+}
+#comp-menu {
+ background-color: #FFFFFF;
+ z-index: 1;
+ padding: 0
+}
+#comp-menu .cont,
+.demo {
+ padding: 0 50px
+}
+#comp-menu h2,
+#comp-menu h2 a {
+ color: #000000;
+ font-weight: lighter;
+ text-transform: inherit
+}
+#comp-menu h2 a:hover {
+ color: #8085e8
+}
+
+.blink_me {
+ animation: blinker 1.5s linear infinite;
+}
+
+@keyframes blinker {
+ 20% { opacity: 0.4; }
+}
+
+
+a.btn.btn-theme {
+ color: #eeeaea;
+ background-color: #007E88;
+ border: 1px solid #007E88;
+ border-bottom: 0;
+ border-radius: 0;
+ font-weight: 400;
+ margin: 0 5px 0 0
+}
+a.btn.btn-theme:hover {
+ color: #90ef7f
+}
+
+
+.btn-book {
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #2e6da4;
+ display: inline-block;
+ white-space: nowrap;
+ vertical-align: middle;
+ border: 1px solid transparent;
+ touch-action: manipulation;
+ cursor: pointer;
+ padding: 2px 6px;
+ font-size: 10px;
+ font-weight: bold;
+ line-height: 1.1333333;
+ border-radius: 6px;
+ background-image: linear-gradient(to bottom,#337ab7 0,#265a88 100%);
+}
+
+a.btn.btn-theme.noselected {
+ background-color: #FFF;
+ color: #313131;
+ opacity: 1
+}
+
+a.btn.btn-theme.disabled {
+ background-color: #FFF;
+ color: #313131;
+ opacity: 1
+}
+.demo {
+ background-color: #f6f6f6
+}
+.demo .demo-name {
+ color: #000000;
+ font-weight: lighter;
+ text-transform: none;
+ padding-left: 15px;
+ text-align: center;
+ display: inline;
+ margin: 0 10px
+}
+#chart-switcher {
+ text-align: center;
+ padding: 30px 0
+}
+.demo #chart-switcher #next-example,
+.demo #chart-switcher #previous-example {
+ font-size: 30px;
+ padding: 0 10px;
+ color: #888
+}
+#demo-buttons {
+ text-align: center;
+ padding: 30px 0
+}
+#demo-buttons a {
+ background-color: #ddd
+}
+#demo-buttons a:hover {
+ background-color: #40818b
+}
+.demo .chart-container {
+ position: relative;
+ background-color: #fff;
+ padding: 30px 0
+}
+.demo .chart-container #previous-example {
+ position: absolute;
+ top: 50%;
+ left: -30px;
+ font-size: 70px;
+ color: #888
+}
+.demo .chart-container #next-example {
+ position: absolute;
+ top: 50%;
+ right: -30px;
+ font-size: 70px;
+ color: #888
+}
+.sidebar-eq-fill {
+ background-color: #f6f6f6
+}
+@media screen and (max-width: 400px) {
+ #chart-switcher,
+ .demo .chart-container,
+ .sidebar-wrapper .sidebar-eq {
+ padding: 0
+ }
+ #small-switcher {
+ text-align: center
+ }
+ .demo #chart-switcher #next-example,
+ .demo #chart-switcher #previous-example {
+ font-size: 20px
+ }
+ #sidebar-close {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: #FFFFFF;
+ color: #fff;
+ border-radius: 0;
+ z-index: 100;
+ cursor: pointer;
+ padding: 10px;
+ visibility: hidden
+ }
+ .toggled #sidebar-close {
+ visibility: visible
+ }
+}
+@media screen and (min-width: 400px) and (max-width: 768px) {
+ .demo {
+ padding: 15px
+ }
+ #sidebar-close {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: #FFFFFF;
+ color: #fff;
+ border-radius: 0;
+ z-index: 100;
+ cursor: pointer;
+ padding: 10px;
+ visibility: hidden
+ }
+ .toggled #sidebar-close {
+ visibility: visible
+ }
+}
+#hs-below {
+ background-color: #eeeaea;
+ font-size: 18px;
+ line-height: 26px
+}
+#hs-below h3 {
+ font-size: 30px;
+ line-height: 30px;
+ font-weight: bolder;
+ margin-top: 0;
+ margin-bottom: 15px
+}
+#hs-below p,
+ul {
+ font-size: 1pc
+}
+#hs-below .box {
+ position: relative;
+ background-color: #fff;
+ padding: 14px 22px
+}
+#hs-below .box .box-content {
+ margin-bottom: 40px
+}
+#hs-below .box .button {
+ position: absolute;
+ bottom: 0;
+ margin-bottom: 10px
+}
+#hs-below .box.purple {
+ border-bottom: 8px solid #8085e8
+}
+#hs-below .box.green {
+ border-bottom: 8px solid #90ef7f
+}
+#hs-below .box .book {
+ text-align: center
+}
+#hs-below .box .book img {
+ max-height: 200px
+}
+@media screen and (max-width: 768px) {
+ #hs-below h3 {
+ margin-top: 30px
+ }
+ #hs-below .box-1 h3 {
+ margin-top: 0
+ }
+ #hs-below .box .book {
+ text-align: left
+ }
+}
+@media screen and (min-width: 768px) and (max-width: 992px) {
+ #hs-below h3 {
+ margin-top: 30px
+ }
+ #hs-below .box-1 h3,
+ #hs-below .box-2 h3 {
+ margin-top: 0
+ }
+ #hs-below .box-1 .box,
+ #hs-below .box-2 .box {
+ min-height: 260px
+ }
+ #hs-below .box-3 .box,
+ #hs-below .box-4 .box {
+ min-height: 280px
+ }
+}
+@media screen and (min-width: 992px) and (max-width: 1200px) {
+ #hs-below h3 {
+ margin-top: 30px
+ }
+ #hs-below .box-1 h3,
+ #hs-below .box-2 h3 {
+ margin-top: 0
+ }
+ #hs-below .box-1 .box,
+ #hs-below .box-2 .box {
+ min-height: 255px
+ }
+ #hs-below .box-3 .box,
+ #hs-below .box-4 .box {
+ min-height: 280px
+ }
+}
+@media screen and (min-width: 1200px) {
+ #hs-below .box {
+ min-height: 390px
+ }
+}
+#hs-bottom,
+#hs-bottom a {
+ color: #eeeaea
+}
+#hs-bottom {
+ background-color: #FFFFFF;
+ font-size: 1pc;
+ line-height: 20px
+}
+#hs-bottom a:focus,
+#hs-bottom a:hover {
+ color: #90ef7f
+}
+#hs-bottom a.button {
+ color: #313131
+}
+#hs-bottom a.button:focus,
+#hs-bottom a.button:hover {
+ color: #eeeaea
+}
+#hs-bottom h3 {
+ font-size: 24px;
+ line-height: 24px;
+ font-weight: lighter
+}
+#hs-bottom h4 {
+ font-size: 14px
+}
+#hs-bottom .grayed {
+ color: #d6d1d1;
+ margin: 0
+}
+@media screen and (max-width: 768px) {
+ #hs-bottom [class*=col-] {
+ border-bottom: 1px dotted #eeeaea;
+ padding-bottom: 15px
+ }
+ #hs-bottom [class*=col-]:nth-child(1) h3 {
+ margin-top: 0
+ }
+ #hs-bottom [class*=col-]:last-child {
+ border-bottom: 0;
+ padding-bottom: 0
+ }
+}
+@media screen and (min-width: 768px) and (max-width: 992px) {
+ #hs-bottom [class*=col-] {
+ min-height: 350px;
+ border-right: 1px dotted #eeeaea
+ }
+ #hs-bottom [class*=col-]:last-child,
+ #hs-bottom [class*=col-]:nth-child(2) {
+ border-right: 0
+ }
+}
+@media screen and (min-width: 992px) and (max-width: 1200px) {
+ #hs-bottom [class*=col-] {
+ min-height: 300px;
+ border-right: 1px dotted #eeeaea
+ }
+ #hs-bottom [class*=col-]:last-child,
+ #hs-bottom [class*=col-]:nth-child(2) {
+ border-right: 0
+ }
+}
+@media screen and (min-width: 1200px) {
+ #hs-bottom [class*=col-] {
+ min-height: 25pc;
+ border-right: 1px dotted #eeeaea
+ }
+ #hs-bottom [class*=col-]:last-child {
+ border-right: 0
+ }
+}
+
+
+
+.button{
+ border:1px solid #333;
+ background:#6479fd;
+}
+.button:hover{
+ background:#a4a9fd;
+}
+.dialog{
+ border:5px solid #666;
+ padding:10px;
+ background:#3A3A3A;
+ position:absolute;
+ display:none;
+}
+.dialog label{
+ display:inline-block;
+ color:#cecece;
+}
+input[type=text]{
+ border:1px solid #333;
+ display:inline-block;
+ margin:5px;
+}
+#btnOK{
+ border:1px solid #000;
+ background:#ff9999;
+ margin:5px;
+}
+
+#btnOK:hover{
+ border:1px solid #000;
+ background:#ffacac;
+}
+
diff --git a/tools/infra-dashboard/fonts/SourceSansPro-Bold.ttf b/tools/infra-dashboard/fonts/SourceSansPro-Bold.ttf
new file mode 100644
index 00000000..62a0a4d9
--- /dev/null
+++ b/tools/infra-dashboard/fonts/SourceSansPro-Bold.ttf
Binary files differ
diff --git a/tools/infra-dashboard/fonts/SourceSansPro-ExtraLight.ttf b/tools/infra-dashboard/fonts/SourceSansPro-ExtraLight.ttf
new file mode 100644
index 00000000..e28a62a1
--- /dev/null
+++ b/tools/infra-dashboard/fonts/SourceSansPro-ExtraLight.ttf
Binary files differ
diff --git a/tools/infra-dashboard/fonts/SourceSansPro-Regular.ttf b/tools/infra-dashboard/fonts/SourceSansPro-Regular.ttf
new file mode 100644
index 00000000..ffe27865
--- /dev/null
+++ b/tools/infra-dashboard/fonts/SourceSansPro-Regular.ttf
Binary files differ
diff --git a/tools/infra-dashboard/fonts/fontawesome-webfont.ttf b/tools/infra-dashboard/fonts/fontawesome-webfont.ttf
new file mode 100644
index 00000000..26dea795
--- /dev/null
+++ b/tools/infra-dashboard/fonts/fontawesome-webfont.ttf
Binary files differ
diff --git a/tools/infra-dashboard/fonts/fontawesome-webfont.woff b/tools/infra-dashboard/fonts/fontawesome-webfont.woff
new file mode 100644
index 00000000..dc35ce3c
--- /dev/null
+++ b/tools/infra-dashboard/fonts/fontawesome-webfont.woff
Binary files differ
diff --git a/tools/infra-dashboard/fonts/fontawesome-webfont.woff2 b/tools/infra-dashboard/fonts/fontawesome-webfont.woff2
new file mode 100644
index 00000000..500e5172
--- /dev/null
+++ b/tools/infra-dashboard/fonts/fontawesome-webfont.woff2
Binary files differ
diff --git a/tools/infra-dashboard/fonts/source-sans-pro.black.ttf b/tools/infra-dashboard/fonts/source-sans-pro.black.ttf
new file mode 100644
index 00000000..f3730088
--- /dev/null
+++ b/tools/infra-dashboard/fonts/source-sans-pro.black.ttf
Binary files differ
diff --git a/tools/infra-dashboard/index.php b/tools/infra-dashboard/index.php
new file mode 100644
index 00000000..56c765a9
--- /dev/null
+++ b/tools/infra-dashboard/index.php
@@ -0,0 +1,353 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>OPNFV Pharos Dashboard | OPNFV</title>
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" type="text/css" />
+ <link rel="stylesheet" href="./css/dataTables.bootstrap.min.css" type="text/css" />
+ <script src="//code.jquery.com/jquery-1.10.2.js"></script>
+ <script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
+ <script src="http://www.itsyndicate.ca/gssi/jquery/jquery.crypt.js"></script>
+ <script src="https://cdn.datatables.net/1.10.11/js/jquery.dataTables.min.js"></script>
+ <script src="https://cdn.datatables.net/1.10.11/js/dataTables.bootstrap.min.js"></script>
+ <link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css"/>
+
+ <link rel="stylesheet" href="./css/template.css" type="text/css" />
+ <link rel="stylesheet" href="./css/theme.css" type="text/css" />
+ <link rel="stylesheet" href="./css/opnfv.css" type="text/css" />
+
+ <link href='./css/fullcalendar.css' rel='stylesheet' />
+ <link href='./css/fullcalendar.print.css' rel='stylesheet' media='print' />
+ <script src='./js/moment.min.js'></script>
+ <script src='./js/fullcalendar.js'></script>
+
+
+ <style>
+ fieldset { padding:0; border:0; margin-top:15px; }
+ input.text { margin-bottom:2px; width:90%; padding: .2em; font-size:14px; }
+ input { display:block; font-size:14px; }
+ label {font-size:14px;}
+ .ui-dialog .ui-state-error { padding: .3em; }
+ .validateTips { border: 1px solid transparent; padding: 0.3em; }
+ .booked_day span {
+ color: red !important; /* should only apply to may 6 and 8 */
+ }
+ </style>
+
+ <script type="text/javascript">
+ $(document).ready(function() {
+
+ function getParameterByName(name, url) {
+ if (!url) url = window.location.href;
+ name = name.replace(/[\[\]]/g, "\\$&");
+ var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
+ results = regex.exec(url);
+ if (!results) return null;
+ if (!results[2]) return '';
+ return decodeURIComponent(results[2].replace(/\+/g, " "));
+ }
+
+ function selectTab(name) {
+ $("#container").empty();
+ var imgName = './media/ajax-loader.gif';
+ document.getElementById('container')
+ .innerHTML = '<img style="position: relative;left: 50%;" src="' + imgName + '" />';
+ if (name == "devpods") {
+ $( "#btn_cipods" ).addClass( "noselected" );
+ $( "#btn_devpods" ).removeClass( "noselected" );
+ $( "#btn_slaves" ).addClass( "noselected" );
+ var key = Math.random();
+ $("#container").load("pages/dev_pods.php?key="+key);
+ $('#hd_page').attr('value', "devpods");
+ }
+ else if (name == "slaves") {
+ $( "#btn_cipods" ).addClass( "noselected" );
+ $( "#btn_devpods" ).addClass( "noselected" );
+ $( "#btn_slaves" ).removeClass( "noselected" );
+ $("#container").load("pages/slaves.php");
+ $('#hd_page').attr('value', "slaves");
+ }
+ else {
+ $( "#btn_cipods" ).removeClass( "noselected" );
+ $( "#btn_devpods" ).addClass( "noselected" );
+ $( "#btn_slaves" ).addClass( "noselected" );
+ $("#container").load("pages/ci_pods.php");
+ $('#hd_page').attr('value', "cipods");
+ }
+ }
+
+
+ var page = getParameterByName('page');
+ if (page == "devpods") selectTab("devpods");
+ else if (page == "slaves") selectTab("slaves");
+ else selectTab("cipods");
+
+
+ $( "#btn_cipods" ).click(function() {
+ selectTab("cipods");
+ });
+ $( "#btn_devpods" ).click(function() {
+ selectTab("devpods");
+ });
+ $( "#btn_slaves" ).click(function() {
+ selectTab("slaves");
+ });
+ } );
+
+
+
+ $(function() {
+ var dialog, form,
+ allFields = $( [] ).add( login_email ).add( login_password );
+ emailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
+
+ function checkLength( o, n, min, max ) {
+ if ( o.val().length > max || o.val().length < min ) {
+ o.addClass( "ui-state-error" );
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ function checkRegexp( o, regexp, n ) {
+ if ( !( regexp.test( o.val() ) ) ) {
+ o.addClass( "ui-state-error" );
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+
+ function login() {
+ var valid = true;
+ email = $( "#login_email" );
+ password = $( "#login_password" );
+ allFields.removeClass( "ui-state-error" );
+
+ valid = valid && checkLength( email, "email", 6, 80 );
+ valid = valid && checkLength( password, "password", 5, 16 );
+
+ valid = valid && checkRegexp( email, emailRegex, "eg. ui@jquery.com" );
+ valid = valid && checkRegexp( password, /^([0-9a-zA-Z])+$/, "Password field only allow : a-z 0-9" );
+
+
+ if ( valid ) {
+ var email = $('#login_email').val();
+ var password = $('#login_password').val();
+ var passwordMD5 = $().crypt({
+ method: "md5",
+ source: password
+ });
+ $.ajax({
+ type: 'POST',
+ url: "utils/login.php",
+ data: {action: 'login', email: email, password: passwordMD5},
+ success: function(data){
+ //alert(data);
+ json = JSON.parse(data);
+ if (json.result == 1) {
+ alert("Wrong password.")
+ } else if (json.result == 2){
+ alert("User not registered.")
+ } else {
+ var page = $('#hd_page').val();
+ location.href = location.protocol + '//' + location.host + location.pathname + "?page=" + page;
+ }
+ },
+ error: function(data){
+ alert(data)
+ }
+ });
+
+ }
+ }
+
+ dialog_login = $( "#dialog-login" ).dialog({
+ autoOpen: false,
+ height: 225,
+ width: 400,
+ modal: true,
+ resizable:false,
+ buttons: {
+ "Login": login,
+ Cancel: function() {
+ dialog_login.dialog( "close" );
+ }
+ },
+ close: function() {
+ form[ 0 ].reset();
+ allFields.removeClass( "ui-state-error" );
+ }
+ });
+
+ form = dialog_login.find( "form" ).on( "submit", function( event ) {
+ event.preventDefault();
+ login();
+ });
+
+
+ $( "#login_text" ).on( "click", function() {
+ dialog_login.dialog( "open" );
+ });
+
+ $( "#logout" ).on( "click", function() {
+ $.ajax({
+ type: 'POST',
+ url: "utils/login.php",
+ data: {action: 'logout'},
+ success: function(data){
+ var page = $('#hd_page').val();
+ location.href = location.protocol + '//' + location.host + location.pathname + "?page=" + page;
+ },
+ error: function(data){
+ alert(data)
+ }
+ });
+ });
+ });
+ </script>
+ </head>
+
+
+ <body>
+
+ <?php
+ session_start();
+ ?>
+
+ <div class="collaborative-projects">
+ <div class="gray-diagonal">
+ <div class="container">
+ <a id="collaborative-projects-logo" href="http://collabprojects.linuxfoundation.org">Linux Foundation Collaborative Projects</a>
+ </div>
+ </div>
+ </div>
+
+
+ <div id="menu">
+ <nav class="navbar navbar-default" role="navigation">
+ <div class="container">
+ <div class="navbar-header">
+ <a class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
+ <span class="sr-only">Toggle navigation</span>
+ </a>
+ <a class="navbar-brand" href="https://www.opnfv.org/" title="OPNFV">
+ <img src="https://www.opnfv.org/sites/all/themes/opnfv/logo.png" alt="OPNFV" />
+ </a>
+ </div>
+ <div class="collapse navbar-collapse">
+ <div id="menu-container">
+ <div id="menu-second" class="hidden-xs">
+ <ul class="nav navbar-nav pull-right">
+
+ <li class="item-112">
+ <a target="_blank" href="https://www.opnfv.org/" >About Us</a>
+ </li>
+ <li class="item-113 deeper dropdown">
+ <a target="_blank" href="#" class="dropdown-toggle" data-toggle="dropdown">Dodumentation
+ <span class="toggle-arrow"></span>
+ </a>
+ <ul class="dropdown-menu" role="menu">
+ <li class="item-121">
+ <a target="_blank" href="http://artifacts.opnfv.org/pharos/docs/" >Pharos</a>
+ </li>
+ <li class="item-122">
+ <a target="_blank" href="" >Releng</a>
+ </li>
+ </ul>
+ </li>
+ <li class="item-218">
+ <a target="_blank" href="https://wiki.opnfv.org/" >OPNFV Wiki</a>
+ </li>
+ <li class="item-114">
+ <a target="_blank" href="" >Contact</a>
+ </li>
+ <li class="item-112">
+ <?php
+
+ if (isset($_SESSION['user_id'])) {
+ echo '<a style="cursor: pointer;" id="logout">Logout</a>';
+ }
+ else {
+ echo '<a style="cursor: pointer;" id="login_text">Login</a>';
+ }
+ ?>
+
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </nav>
+ </div>
+
+
+ <div id="hs-component">
+ <div class="container">
+ <div id="wrap" class="sidebar-wrapper">
+ <div id="comp-menu" class="col-lg-12 col-md-12 col-sm-12 col-xs-12 hidden-xs">
+ <?php
+ if (isset($_SESSION['user_id'])) {
+ echo '<div style="float:right;text-align:right;top:0;margin-right:18px">';
+ echo 'current user: '.$_SESSION['user_name'];
+ echo '</div>';
+ }
+ ?>
+ <h2 class="demo-name">Pharos Infrastructure</h2>
+
+ <div class="btn-group theme">
+ <a id="btn_cipods" class="btn btn-theme noselected">CI PODs</a>
+ <a id="btn_devpods" class="btn btn-theme noselected">DEVELOPMENT PODs</a>
+ <a id="btn_slaves" class="btn btn-theme noselected">JENKINS SLAVES</a>
+ </div>
+
+ <div style="min-width: 310px; height: 2px; margin: 0 auto; background-color: #007E88"></div>
+ <div style="min-width: 310px; height: 20px; margin: 0 auto; background-color: #ffffff"></div>
+ </div>
+
+ <div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>
+ </div>
+ </div>
+ </div>
+
+
+
+ <div id="dialog-login" title="Login">
+ <form>
+ <table>
+ <tr>
+ <td><label for="login_email">Email</label></td>
+ <td><input type="text" label="Email" name="login_email" id="login_email" value="" size="30" class="text ui-widget-content ui-corner-all"/></td>
+ </tr>
+ <td><label for="login_password">Password</label></td>
+ <td><input type="password" name="login_password" id="login_password" size="30" value="" class="text ui-widget-content ui-corner-all"/></td>
+ <tr>
+ </tr>
+ </table>
+ <input type="submit" tabindex="-1" style="position:absolute; top:-100px"/>
+ </form>
+ </div>
+
+
+ <div id="footer" style="float:bottom">
+ <div class="container">
+ <div class="col-lg-6 col-md-6 col-sm-6 col-xs-12">
+ <div id="zt-footer-copy">
+ Maintained by jose.lausuch@ericsson.com.
+ </div>
+ </div>
+ <div class="socials"></div>
+ </div>
+
+ <?php
+ echo '<input type="hidden" id="hd_user_id" value="'.$_SESSION['user_id'].'"/>';
+ echo '<input type="hidden" id="hd_user_email" value="'.$_SESSION['user_email'].'"/>';
+ echo '<input type="hidden" id="hd_user_name" value="'.$_SESSION['user_name'].'"/>';
+ ?>
+ <input type="hidden" id="hd_page" value="cipods"/>
+
+ </body>
+</html>
diff --git a/tools/infra-dashboard/js/bootstrap.js b/tools/infra-dashboard/js/bootstrap.js
new file mode 100644
index 00000000..b2ff4e83
--- /dev/null
+++ b/tools/infra-dashboard/js/bootstrap.js
@@ -0,0 +1,2118 @@
+/*!
+ * Bootstrap v3.2.0 (http://getbootstrap.com)
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+/*!
+ * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=e82eeb1f3b04b506268e)
+ * Config saved to config.json and https://gist.github.com/e82eeb1f3b04b506268e
+ */
+if (typeof jQuery === "undefined") { throw new Error("Bootstrap's JavaScript requires jQuery") }
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.2.0
+ * http://getbootstrap.com/javascript/#alerts
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // ALERT CLASS DEFINITION
+ // ======================
+
+ var dismiss = '[data-dismiss="alert"]'
+ var Alert = function (el) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.VERSION = '3.2.0'
+
+ Alert.prototype.close = function (e) {
+ var $this = $(this)
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ var $parent = $(selector)
+
+ if (e) e.preventDefault()
+
+ if (!$parent.length) {
+ $parent = $this.hasClass('alert') ? $this : $this.parent()
+ }
+
+ $parent.trigger(e = $.Event('close.bs.alert'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent.removeClass('in')
+
+ function removeElement() {
+ // detach from parent, fire event then clean up data
+ $parent.detach().trigger('closed.bs.alert').remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent
+ .one('bsTransitionEnd', removeElement)
+ .emulateTransitionEnd(150) :
+ removeElement()
+ }
+
+
+ // ALERT PLUGIN DEFINITION
+ // =======================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.alert')
+
+ if (!data) $this.data('bs.alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ var old = $.fn.alert
+
+ $.fn.alert = Plugin
+ $.fn.alert.Constructor = Alert
+
+
+ // ALERT NO CONFLICT
+ // =================
+
+ $.fn.alert.noConflict = function () {
+ $.fn.alert = old
+ return this
+ }
+
+
+ // ALERT DATA-API
+ // ==============
+
+ $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.2.0
+ * http://getbootstrap.com/javascript/#buttons
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // BUTTON PUBLIC CLASS DEFINITION
+ // ==============================
+
+ var Button = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, Button.DEFAULTS, options)
+ this.isLoading = false
+ }
+
+ Button.VERSION = '3.2.0'
+
+ Button.DEFAULTS = {
+ loadingText: 'loading...'
+ }
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled'
+ var $el = this.$element
+ var val = $el.is('input') ? 'val' : 'html'
+ var data = $el.data()
+
+ state = state + 'Text'
+
+ if (data.resetText == null) $el.data('resetText', $el[val]())
+
+ $el[val](data[state] == null ? this.options[state] : data[state])
+
+ // push to event loop to allow forms to submit
+ setTimeout($.proxy(function () {
+ if (state == 'loadingText') {
+ this.isLoading = true
+ $el.addClass(d).attr(d, d)
+ } else if (this.isLoading) {
+ this.isLoading = false
+ $el.removeClass(d).removeAttr(d)
+ }
+ }, this), 0)
+ }
+
+ Button.prototype.toggle = function () {
+ var changed = true
+ var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+ if ($parent.length) {
+ var $input = this.$element.find('input')
+ if ($input.prop('type') == 'radio') {
+ if ($input.prop('checked') && this.$element.hasClass('active')) changed = false
+ else $parent.find('.active').removeClass('active')
+ }
+ if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')
+ }
+
+ if (changed) this.$element.toggleClass('active')
+ }
+
+
+ // BUTTON PLUGIN DEFINITION
+ // ========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.button')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+ if (option == 'toggle') data.toggle()
+ else if (option) data.setState(option)
+ })
+ }
+
+ var old = $.fn.button
+
+ $.fn.button = Plugin
+ $.fn.button.Constructor = Button
+
+
+ // BUTTON NO CONFLICT
+ // ==================
+
+ $.fn.button.noConflict = function () {
+ $.fn.button = old
+ return this
+ }
+
+
+ // BUTTON DATA-API
+ // ===============
+
+ $(document).on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+ var $btn = $(e.target)
+ if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+ Plugin.call($btn, 'toggle')
+ e.preventDefault()
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.2.0
+ * http://getbootstrap.com/javascript/#carousel
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // CAROUSEL CLASS DEFINITION
+ // =========================
+
+ var Carousel = function (element, options) {
+ this.$element = $(element).on('keydown.bs.carousel', $.proxy(this.keydown, this))
+ this.$indicators = this.$element.find('.carousel-indicators')
+ this.options = options
+ this.paused =
+ this.sliding =
+ this.interval =
+ this.$active =
+ this.$items = null
+
+ this.options.pause == 'hover' && this.$element
+ .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
+ .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
+ }
+
+ Carousel.VERSION = '3.2.0'
+
+ Carousel.DEFAULTS = {
+ interval: 5000,
+ pause: 'hover',
+ wrap: true
+ }
+
+ Carousel.prototype.keydown = function (e) {
+ switch (e.which) {
+ case 37: this.prev(); break
+ case 39: this.next(); break
+ default: return
+ }
+
+ e.preventDefault()
+ }
+
+ Carousel.prototype.cycle = function (e) {
+ e || (this.paused = false)
+
+ this.interval && clearInterval(this.interval)
+
+ this.options.interval
+ && !this.paused
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+ return this
+ }
+
+ Carousel.prototype.getItemIndex = function (item) {
+ this.$items = item.parent().children('.item')
+ return this.$items.index(item || this.$active)
+ }
+
+ Carousel.prototype.to = function (pos) {
+ var that = this
+ var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
+
+ if (pos > (this.$items.length - 1) || pos < 0) return
+
+ if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
+ if (activeIndex == pos) return this.pause().cycle()
+
+ return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
+ }
+
+ Carousel.prototype.pause = function (e) {
+ e || (this.paused = true)
+
+ if (this.$element.find('.next, .prev').length && $.support.transition) {
+ this.$element.trigger($.support.transition.end)
+ this.cycle(true)
+ }
+
+ this.interval = clearInterval(this.interval)
+
+ return this
+ }
+
+ Carousel.prototype.next = function () {
+ if (this.sliding) return
+ return this.slide('next')
+ }
+
+ Carousel.prototype.prev = function () {
+ if (this.sliding) return
+ return this.slide('prev')
+ }
+
+ Carousel.prototype.slide = function (type, next) {
+ var $active = this.$element.find('.item.active')
+ var $next = next || $active[type]()
+ var isCycling = this.interval
+ var direction = type == 'next' ? 'left' : 'right'
+ var fallback = type == 'next' ? 'first' : 'last'
+ var that = this
+
+ if (!$next.length) {
+ if (!this.options.wrap) return
+ $next = this.$element.find('.item')[fallback]()
+ }
+
+ if ($next.hasClass('active')) return (this.sliding = false)
+
+ var relatedTarget = $next[0]
+ var slideEvent = $.Event('slide.bs.carousel', {
+ relatedTarget: relatedTarget,
+ direction: direction
+ })
+ this.$element.trigger(slideEvent)
+ if (slideEvent.isDefaultPrevented()) return
+
+ this.sliding = true
+
+ isCycling && this.pause()
+
+ if (this.$indicators.length) {
+ this.$indicators.find('.active').removeClass('active')
+ var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
+ $nextIndicator && $nextIndicator.addClass('active')
+ }
+
+ var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
+ if ($.support.transition && this.$element.hasClass('slide')) {
+ $next.addClass(type)
+ $next[0].offsetWidth // force reflow
+ $active.addClass(direction)
+ $next.addClass(direction)
+ $active
+ .one('bsTransitionEnd', function () {
+ $next.removeClass([type, direction].join(' ')).addClass('active')
+ $active.removeClass(['active', direction].join(' '))
+ that.sliding = false
+ setTimeout(function () {
+ that.$element.trigger(slidEvent)
+ }, 0)
+ })
+ .emulateTransitionEnd($active.css('transition-duration').slice(0, -1) * 1000)
+ } else {
+ $active.removeClass('active')
+ $next.addClass('active')
+ this.sliding = false
+ this.$element.trigger(slidEvent)
+ }
+
+ isCycling && this.cycle()
+
+ return this
+ }
+
+
+ // CAROUSEL PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.carousel')
+ var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ var action = typeof option == 'string' ? option : options.slide
+
+ if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+ if (typeof option == 'number') data.to(option)
+ else if (action) data[action]()
+ else if (options.interval) data.pause().cycle()
+ })
+ }
+
+ var old = $.fn.carousel
+
+ $.fn.carousel = Plugin
+ $.fn.carousel.Constructor = Carousel
+
+
+ // CAROUSEL NO CONFLICT
+ // ====================
+
+ $.fn.carousel.noConflict = function () {
+ $.fn.carousel = old
+ return this
+ }
+
+
+ // CAROUSEL DATA-API
+ // =================
+
+ $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
+ var href
+ var $this = $(this)
+ var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
+ if (!$target.hasClass('carousel')) return
+ var options = $.extend({}, $target.data(), $this.data())
+ var slideIndex = $this.attr('data-slide-to')
+ if (slideIndex) options.interval = false
+
+ Plugin.call($target, options)
+
+ if (slideIndex) {
+ $target.data('bs.carousel').to(slideIndex)
+ }
+
+ e.preventDefault()
+ })
+
+ $(window).on('load', function () {
+ $('[data-ride="carousel"]').each(function () {
+ var $carousel = $(this)
+ Plugin.call($carousel, $carousel.data())
+ })
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.2.0
+ * http://getbootstrap.com/javascript/#dropdowns
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // DROPDOWN CLASS DEFINITION
+ // =========================
+
+ var backdrop = '.dropdown-backdrop'
+ var toggle = '[data-toggle="dropdown"]'
+ var Dropdown = function (element) {
+ $(element).on('click.bs.dropdown', this.toggle)
+ }
+
+ Dropdown.VERSION = '3.2.0'
+
+ Dropdown.prototype.toggle = function (e) {
+ var $this = $(this)
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+ // if mobile we use a backdrop because click events don't delegate
+ $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
+ }
+
+ var relatedTarget = { relatedTarget: this }
+ $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
+
+ if (e.isDefaultPrevented()) return
+
+ $this.trigger('focus')
+
+ $parent
+ .toggleClass('open')
+ .trigger('shown.bs.dropdown', relatedTarget)
+ }
+
+ return false
+ }
+
+ Dropdown.prototype.keydown = function (e) {
+ if (!/(38|40|27)/.test(e.keyCode)) return
+
+ var $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ if (!isActive || (isActive && e.keyCode == 27)) {
+ if (e.which == 27) $parent.find(toggle).trigger('focus')
+ return $this.trigger('click')
+ }
+
+ var desc = ' li:not(.divider):visible a'
+ var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)
+
+ if (!$items.length) return
+
+ var index = $items.index($items.filter(':focus'))
+
+ if (e.keyCode == 38 && index > 0) index-- // up
+ if (e.keyCode == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items.eq(index).trigger('focus')
+ }
+
+ function clearMenus(e) {
+ if (e && e.which === 3) return
+ $(backdrop).remove()
+ $(toggle).each(function () {
+ var $parent = getParent($(this))
+ var relatedTarget = { relatedTarget: this }
+ if (!$parent.hasClass('open')) return
+ $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
+ if (e.isDefaultPrevented()) return
+ $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
+ })
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ var $parent = selector && $(selector)
+
+ return $parent && $parent.length ? $parent : $this.parent()
+ }
+
+
+ // DROPDOWN PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.dropdown')
+
+ if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ var old = $.fn.dropdown
+
+ $.fn.dropdown = Plugin
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ // DROPDOWN NO CONFLICT
+ // ====================
+
+ $.fn.dropdown.noConflict = function () {
+ $.fn.dropdown = old
+ return this
+ }
+
+
+ // APPLY TO STANDARD DROPDOWN ELEMENTS
+ // ===================================
+
+ $(document)
+ .on('click.bs.dropdown.data-api', clearMenus)
+ .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+ .on('keydown.bs.dropdown.data-api', toggle + ', [role="menu"], [role="listbox"]', Dropdown.prototype.keydown)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.2.0
+ * http://getbootstrap.com/javascript/#modals
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // MODAL CLASS DEFINITION
+ // ======================
+
+ var Modal = function (element, options) {
+ this.options = options
+ this.$body = $(document.body)
+ this.$element = $(element)
+ this.$backdrop =
+ this.isShown = null
+ this.scrollbarWidth = 0
+
+ if (this.options.remote) {
+ this.$element
+ .find('.modal-content')
+ .load(this.options.remote, $.proxy(function () {
+ this.$element.trigger('loaded.bs.modal')
+ }, this))
+ }
+ }
+
+ Modal.VERSION = '3.2.0'
+
+ Modal.DEFAULTS = {
+ backdrop: true,
+ keyboard: true,
+ show: true
+ }
+
+ Modal.prototype.toggle = function (_relatedTarget) {
+ return this.isShown ? this.hide() : this.show(_relatedTarget)
+ }
+
+ Modal.prototype.show = function (_relatedTarget) {
+ var that = this
+ var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
+
+ this.$element.trigger(e)
+
+ if (this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = true
+
+ this.checkScrollbar()
+ this.$body.addClass('modal-open')
+
+ this.setScrollbar()
+ this.escape()
+
+ this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+
+ this.backdrop(function () {
+ var transition = $.support.transition && that.$element.hasClass('fade')
+
+ if (!that.$element.parent().length) {
+ that.$element.appendTo(that.$body) // don't move modals dom position
+ }
+
+ that.$element
+ .show()
+ .scrollTop(0)
+
+ if (transition) {
+ that.$element[0].offsetWidth // force reflow
+ }
+
+ that.$element
+ .addClass('in')
+ .attr('aria-hidden', false)
+
+ that.enforceFocus()
+
+ var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
+
+ transition ?
+ that.$element.find('.modal-dialog') // wait for modal to slide in
+ .one('bsTransitionEnd', function () {
+ that.$element.trigger('focus').trigger(e)
+ })
+ .emulateTransitionEnd(300) :
+ that.$element.trigger('focus').trigger(e)
+ })
+ }
+
+ Modal.prototype.hide = function (e) {
+ if (e) e.preventDefault()
+
+ e = $.Event('hide.bs.modal')
+
+ this.$element.trigger(e)
+
+ if (!this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = false
+
+ this.$body.removeClass('modal-open')
+
+ this.resetScrollbar()
+ this.escape()
+
+ $(document).off('focusin.bs.modal')
+
+ this.$element
+ .removeClass('in')
+ .attr('aria-hidden', true)
+ .off('click.dismiss.bs.modal')
+
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.$element
+ .one('bsTransitionEnd', $.proxy(this.hideModal, this))
+ .emulateTransitionEnd(300) :
+ this.hideModal()
+ }
+
+ Modal.prototype.enforceFocus = function () {
+ $(document)
+ .off('focusin.bs.modal') // guard against infinite focus loop
+ .on('focusin.bs.modal', $.proxy(function (e) {
+ if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
+ this.$element.trigger('focus')
+ }
+ }, this))
+ }
+
+ Modal.prototype.escape = function () {
+ if (this.isShown && this.options.keyboard) {
+ this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) {
+ e.which == 27 && this.hide()
+ }, this))
+ } else if (!this.isShown) {
+ this.$element.off('keyup.dismiss.bs.modal')
+ }
+ }
+
+ Modal.prototype.hideModal = function () {
+ var that = this
+ this.$element.hide()
+ this.backdrop(function () {
+ that.$element.trigger('hidden.bs.modal')
+ })
+ }
+
+ Modal.prototype.removeBackdrop = function () {
+ this.$backdrop && this.$backdrop.remove()
+ this.$backdrop = null
+ }
+
+ Modal.prototype.backdrop = function (callback) {
+ var that = this
+ var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+ if (this.isShown && this.options.backdrop) {
+ var doAnimate = $.support.transition && animate
+
+ this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+ .appendTo(this.$body)
+
+ this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
+ if (e.target !== e.currentTarget) return
+ this.options.backdrop == 'static'
+ ? this.$element[0].focus.call(this.$element[0])
+ : this.hide.call(this)
+ }, this))
+
+ if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+ this.$backdrop.addClass('in')
+
+ if (!callback) return
+
+ doAnimate ?
+ this.$backdrop
+ .one('bsTransitionEnd', callback)
+ .emulateTransitionEnd(150) :
+ callback()
+
+ } else if (!this.isShown && this.$backdrop) {
+ this.$backdrop.removeClass('in')
+
+ var callbackRemove = function () {
+ that.removeBackdrop()
+ callback && callback()
+ }
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.$backdrop
+ .one('bsTransitionEnd', callbackRemove)
+ .emulateTransitionEnd(150) :
+ callbackRemove()
+
+ } else if (callback) {
+ callback()
+ }
+ }
+
+ Modal.prototype.checkScrollbar = function () {
+ if (document.body.clientWidth >= window.innerWidth) return
+ this.scrollbarWidth = this.scrollbarWidth || this.measureScrollbar()
+ }
+
+ Modal.prototype.setScrollbar = function () {
+ var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
+ if (this.scrollbarWidth) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
+ }
+
+ Modal.prototype.resetScrollbar = function () {
+ this.$body.css('padding-right', '')
+ }
+
+ Modal.prototype.measureScrollbar = function () { // thx walsh
+ var scrollDiv = document.createElement('div')
+ scrollDiv.className = 'modal-scrollbar-measure'
+ this.$body.append(scrollDiv)
+ var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
+ this.$body[0].removeChild(scrollDiv)
+ return scrollbarWidth
+ }
+
+
+ // MODAL PLUGIN DEFINITION
+ // =======================
+
+ function Plugin(option, _relatedTarget) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.modal')
+ var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+ if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+ if (typeof option == 'string') data[option](_relatedTarget)
+ else if (options.show) data.show(_relatedTarget)
+ })
+ }
+
+ var old = $.fn.modal
+
+ $.fn.modal = Plugin
+ $.fn.modal.Constructor = Modal
+
+
+ // MODAL NO CONFLICT
+ // =================
+
+ $.fn.modal.noConflict = function () {
+ $.fn.modal = old
+ return this
+ }
+
+
+ // MODAL DATA-API
+ // ==============
+
+ $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+ var $this = $(this)
+ var href = $this.attr('href')
+ var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
+ var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+ if ($this.is('a')) e.preventDefault()
+
+ $target.one('show.bs.modal', function (showEvent) {
+ if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
+ $target.one('hidden.bs.modal', function () {
+ $this.is(':visible') && $this.trigger('focus')
+ })
+ })
+ Plugin.call($target, option, this)
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.2.0
+ * http://getbootstrap.com/javascript/#tooltip
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // TOOLTIP PUBLIC CLASS DEFINITION
+ // ===============================
+
+ var Tooltip = function (element, options) {
+ this.type =
+ this.options =
+ this.enabled =
+ this.timeout =
+ this.hoverState =
+ this.$element = null
+
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.VERSION = '3.2.0'
+
+ Tooltip.DEFAULTS = {
+ animation: true,
+ placement: 'top',
+ selector: false,
+ template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
+ trigger: 'hover focus',
+ title: '',
+ delay: 0,
+ html: false,
+ container: false,
+ viewport: {
+ selector: 'body',
+ padding: 0
+ }
+ }
+
+ Tooltip.prototype.init = function (type, element, options) {
+ this.enabled = true
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+ this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)
+
+ var triggers = this.options.trigger.split(' ')
+
+ for (var i = triggers.length; i--;) {
+ var trigger = triggers[i]
+
+ if (trigger == 'click') {
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+ } else if (trigger != 'manual') {
+ var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
+ var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
+
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+ }
+ }
+
+ this.options.selector ?
+ (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+ this.fixTitle()
+ }
+
+ Tooltip.prototype.getDefaults = function () {
+ return Tooltip.DEFAULTS
+ }
+
+ Tooltip.prototype.getOptions = function (options) {
+ options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay,
+ hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ Tooltip.prototype.getDelegateOptions = function () {
+ var options = {}
+ var defaults = this.getDefaults()
+
+ this._options && $.each(this._options, function (key, value) {
+ if (defaults[key] != value) options[key] = value
+ })
+
+ return options
+ }
+
+ Tooltip.prototype.enter = function (obj) {
+ var self = obj instanceof this.constructor ?
+ obj : $(obj.currentTarget).data('bs.' + this.type)
+
+ if (!self) {
+ self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+ $(obj.currentTarget).data('bs.' + this.type, self)
+ }
+
+ clearTimeout(self.timeout)
+
+ self.hoverState = 'in'
+
+ if (!self.options.delay || !self.options.delay.show) return self.show()
+
+ self.timeout = setTimeout(function () {
+ if (self.hoverState == 'in') self.show()
+ }, self.options.delay.show)
+ }
+
+ Tooltip.prototype.leave = function (obj) {
+ var self = obj instanceof this.constructor ?
+ obj : $(obj.currentTarget).data('bs.' + this.type)
+
+ if (!self) {
+ self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+ $(obj.currentTarget).data('bs.' + this.type, self)
+ }
+
+ clearTimeout(self.timeout)
+
+ self.hoverState = 'out'
+
+ if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+ self.timeout = setTimeout(function () {
+ if (self.hoverState == 'out') self.hide()
+ }, self.options.delay.hide)
+ }
+
+ Tooltip.prototype.show = function () {
+ var e = $.Event('show.bs.' + this.type)
+
+ if (this.hasContent() && this.enabled) {
+ this.$element.trigger(e)
+
+ var inDom = $.contains(document.documentElement, this.$element[0])
+ if (e.isDefaultPrevented() || !inDom) return
+ var that = this
+
+ var $tip = this.tip()
+
+ var tipId = this.getUID(this.type)
+
+ this.setContent()
+ $tip.attr('id', tipId)
+ this.$element.attr('aria-describedby', tipId)
+
+ if (this.options.animation) $tip.addClass('fade')
+
+ var placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ var autoToken = /\s?auto?\s?/i
+ var autoPlace = autoToken.test(placement)
+ if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+ $tip
+ .detach()
+ .css({ top: 0, left: 0, display: 'block' })
+ .addClass(placement)
+ .data('bs.' + this.type, this)
+
+ this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+
+ var pos = this.getPosition()
+ var actualWidth = $tip[0].offsetWidth
+ var actualHeight = $tip[0].offsetHeight
+
+ if (autoPlace) {
+ var orgPlacement = placement
+ var $parent = this.$element.parent()
+ var parentDim = this.getPosition($parent)
+
+ placement = placement == 'bottom' && pos.top + pos.height + actualHeight - parentDim.scroll > parentDim.height ? 'top' :
+ placement == 'top' && pos.top - parentDim.scroll - actualHeight < 0 ? 'bottom' :
+ placement == 'right' && pos.right + actualWidth > parentDim.width ? 'left' :
+ placement == 'left' && pos.left - actualWidth < parentDim.left ? 'right' :
+ placement
+
+ $tip
+ .removeClass(orgPlacement)
+ .addClass(placement)
+ }
+
+ var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
+
+ this.applyPlacement(calculatedOffset, placement)
+
+ var complete = function () {
+ that.$element.trigger('shown.bs.' + that.type)
+ that.hoverState = null
+ }
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ $tip
+ .one('bsTransitionEnd', complete)
+ .emulateTransitionEnd(150) :
+ complete()
+ }
+ }
+
+ Tooltip.prototype.applyPlacement = function (offset, placement) {
+ var $tip = this.tip()
+ var width = $tip[0].offsetWidth
+ var height = $tip[0].offsetHeight
+
+ // manually read margins because getBoundingClientRect includes difference
+ var marginTop = parseInt($tip.css('margin-top'), 10)
+ var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+ // we must check for NaN for ie 8/9
+ if (isNaN(marginTop)) marginTop = 0
+ if (isNaN(marginLeft)) marginLeft = 0
+
+ offset.top = offset.top + marginTop
+ offset.left = offset.left + marginLeft
+
+ // $.fn.offset doesn't round pixel values
+ // so we use setOffset directly with our own function B-0
+ $.offset.setOffset($tip[0], $.extend({
+ using: function (props) {
+ $tip.css({
+ top: Math.round(props.top),
+ left: Math.round(props.left)
+ })
+ }
+ }, offset), 0)
+
+ $tip.addClass('in')
+
+ // check to see if placing tip in new offset caused the tip to resize itself
+ var actualWidth = $tip[0].offsetWidth
+ var actualHeight = $tip[0].offsetHeight
+
+ if (placement == 'top' && actualHeight != height) {
+ offset.top = offset.top + height - actualHeight
+ }
+
+ var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
+
+ if (delta.left) offset.left += delta.left
+ else offset.top += delta.top
+
+ var arrowDelta = delta.left ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
+ var arrowPosition = delta.left ? 'left' : 'top'
+ var arrowOffsetPosition = delta.left ? 'offsetWidth' : 'offsetHeight'
+
+ $tip.offset(offset)
+ this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], arrowPosition)
+ }
+
+ Tooltip.prototype.replaceArrow = function (delta, dimension, position) {
+ this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + '%') : '')
+ }
+
+ Tooltip.prototype.setContent = function () {
+ var $tip = this.tip()
+ var title = this.getTitle()
+
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ Tooltip.prototype.hide = function () {
+ var that = this
+ var $tip = this.tip()
+ var e = $.Event('hide.bs.' + this.type)
+
+ this.$element.removeAttr('aria-describedby')
+
+ function complete() {
+ if (that.hoverState != 'in') $tip.detach()
+ that.$element.trigger('hidden.bs.' + that.type)
+ }
+
+ this.$element.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ $tip.removeClass('in')
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ $tip
+ .one('bsTransitionEnd', complete)
+ .emulateTransitionEnd(150) :
+ complete()
+
+ this.hoverState = null
+
+ return this
+ }
+
+ Tooltip.prototype.fixTitle = function () {
+ var $e = this.$element
+ if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
+ $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+ }
+ }
+
+ Tooltip.prototype.hasContent = function () {
+ return this.getTitle()
+ }
+
+ Tooltip.prototype.getPosition = function ($element) {
+ $element = $element || this.$element
+ var el = $element[0]
+ var isBody = el.tagName == 'BODY'
+ return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : null, {
+ scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop(),
+ width: isBody ? $(window).width() : $element.outerWidth(),
+ height: isBody ? $(window).height() : $element.outerHeight()
+ }, isBody ? { top: 0, left: 0 } : $element.offset())
+ }
+
+ Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
+ return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+ placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+ placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+ /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
+
+ }
+
+ Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
+ var delta = { top: 0, left: 0 }
+ if (!this.$viewport) return delta
+
+ var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
+ var viewportDimensions = this.getPosition(this.$viewport)
+
+ if (/right|left/.test(placement)) {
+ var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
+ var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
+ if (topEdgeOffset < viewportDimensions.top) { // top overflow
+ delta.top = viewportDimensions.top - topEdgeOffset
+ } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
+ delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
+ }
+ } else {
+ var leftEdgeOffset = pos.left - viewportPadding
+ var rightEdgeOffset = pos.left + viewportPadding + actualWidth
+ if (leftEdgeOffset < viewportDimensions.left) { // left overflow
+ delta.left = viewportDimensions.left - leftEdgeOffset
+ } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
+ delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
+ }
+ }
+
+ return delta
+ }
+
+ Tooltip.prototype.getTitle = function () {
+ var title
+ var $e = this.$element
+ var o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ return title
+ }
+
+ Tooltip.prototype.getUID = function (prefix) {
+ do prefix += ~~(Math.random() * 1000000)
+ while (document.getElementById(prefix))
+ return prefix
+ }
+
+ Tooltip.prototype.tip = function () {
+ return (this.$tip = this.$tip || $(this.options.template))
+ }
+
+ Tooltip.prototype.arrow = function () {
+ return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
+ }
+
+ Tooltip.prototype.validate = function () {
+ if (!this.$element[0].parentNode) {
+ this.hide()
+ this.$element = null
+ this.options = null
+ }
+ }
+
+ Tooltip.prototype.enable = function () {
+ this.enabled = true
+ }
+
+ Tooltip.prototype.disable = function () {
+ this.enabled = false
+ }
+
+ Tooltip.prototype.toggleEnabled = function () {
+ this.enabled = !this.enabled
+ }
+
+ Tooltip.prototype.toggle = function (e) {
+ var self = this
+ if (e) {
+ self = $(e.currentTarget).data('bs.' + this.type)
+ if (!self) {
+ self = new this.constructor(e.currentTarget, this.getDelegateOptions())
+ $(e.currentTarget).data('bs.' + this.type, self)
+ }
+ }
+
+ self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+ }
+
+ Tooltip.prototype.destroy = function () {
+ clearTimeout(this.timeout)
+ this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
+ }
+
+
+ // TOOLTIP PLUGIN DEFINITION
+ // =========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.tooltip')
+ var options = typeof option == 'object' && option
+
+ if (!data && option == 'destroy') return
+ if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.tooltip
+
+ $.fn.tooltip = Plugin
+ $.fn.tooltip.Constructor = Tooltip
+
+
+ // TOOLTIP NO CONFLICT
+ // ===================
+
+ $.fn.tooltip.noConflict = function () {
+ $.fn.tooltip = old
+ return this
+ }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.2.0
+ * http://getbootstrap.com/javascript/#popovers
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // POPOVER PUBLIC CLASS DEFINITION
+ // ===============================
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+ if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+ Popover.VERSION = '3.2.0'
+
+ Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
+ placement: 'right',
+ trigger: 'click',
+ content: '',
+ template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+ })
+
+
+ // NOTE: POPOVER EXTENDS tooltip.js
+ // ================================
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+ Popover.prototype.constructor = Popover
+
+ Popover.prototype.getDefaults = function () {
+ return Popover.DEFAULTS
+ }
+
+ Popover.prototype.setContent = function () {
+ var $tip = this.tip()
+ var title = this.getTitle()
+ var content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content').empty()[ // we use append for html objects to maintain js events
+ this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
+ ](content)
+
+ $tip.removeClass('fade top bottom left right in')
+
+ // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+ // this manually by checking the contents.
+ if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+ }
+
+ Popover.prototype.hasContent = function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ Popover.prototype.getContent = function () {
+ var $e = this.$element
+ var o = this.options
+
+ return $e.attr('data-content')
+ || (typeof o.content == 'function' ?
+ o.content.call($e[0]) :
+ o.content)
+ }
+
+ Popover.prototype.arrow = function () {
+ return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
+ }
+
+ Popover.prototype.tip = function () {
+ if (!this.$tip) this.$tip = $(this.options.template)
+ return this.$tip
+ }
+
+
+ // POPOVER PLUGIN DEFINITION
+ // =========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.popover')
+ var options = typeof option == 'object' && option
+
+ if (!data && option == 'destroy') return
+ if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.popover
+
+ $.fn.popover = Plugin
+ $.fn.popover.Constructor = Popover
+
+
+ // POPOVER NO CONFLICT
+ // ===================
+
+ $.fn.popover.noConflict = function () {
+ $.fn.popover = old
+ return this
+ }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.2.0
+ * http://getbootstrap.com/javascript/#tabs
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // TAB CLASS DEFINITION
+ // ====================
+
+ var Tab = function (element) {
+ this.element = $(element)
+ }
+
+ Tab.VERSION = '3.2.0'
+
+ Tab.prototype.show = function () {
+ var $this = this.element
+ var $ul = $this.closest('ul:not(.dropdown-menu)')
+ var selector = $this.data('target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ if ($this.parent('li').hasClass('active')) return
+
+ var previous = $ul.find('.active:last a')[0]
+ var e = $.Event('show.bs.tab', {
+ relatedTarget: previous
+ })
+
+ $this.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ var $target = $(selector)
+
+ this.activate($this.closest('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $this.trigger({
+ type: 'shown.bs.tab',
+ relatedTarget: previous
+ })
+ })
+ }
+
+ Tab.prototype.activate = function (element, container, callback) {
+ var $active = container.find('> .active')
+ var transition = callback
+ && $.support.transition
+ && $active.hasClass('fade')
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+
+ element.addClass('active')
+
+ if (transition) {
+ element[0].offsetWidth // reflow for transition
+ element.addClass('in')
+ } else {
+ element.removeClass('fade')
+ }
+
+ if (element.parent('.dropdown-menu')) {
+ element.closest('li.dropdown').addClass('active')
+ }
+
+ callback && callback()
+ }
+
+ transition ?
+ $active
+ .one('bsTransitionEnd', next)
+ .emulateTransitionEnd(150) :
+ next()
+
+ $active.removeClass('in')
+ }
+
+
+ // TAB PLUGIN DEFINITION
+ // =====================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.tab')
+
+ if (!data) $this.data('bs.tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.tab
+
+ $.fn.tab = Plugin
+ $.fn.tab.Constructor = Tab
+
+
+ // TAB NO CONFLICT
+ // ===============
+
+ $.fn.tab.noConflict = function () {
+ $.fn.tab = old
+ return this
+ }
+
+
+ // TAB DATA-API
+ // ============
+
+ $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+ e.preventDefault()
+ Plugin.call($(this), 'show')
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.2.0
+ * http://getbootstrap.com/javascript/#affix
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // AFFIX CLASS DEFINITION
+ // ======================
+
+ var Affix = function (element, options) {
+ this.options = $.extend({}, Affix.DEFAULTS, options)
+
+ this.$target = $(this.options.target)
+ .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+ .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
+
+ this.$element = $(element)
+ this.affixed =
+ this.unpin =
+ this.pinnedOffset = null
+
+ this.checkPosition()
+ }
+
+ Affix.VERSION = '3.2.0'
+
+ Affix.RESET = 'affix affix-top affix-bottom'
+
+ Affix.DEFAULTS = {
+ offset: 0,
+ target: window
+ }
+
+ Affix.prototype.getPinnedOffset = function () {
+ if (this.pinnedOffset) return this.pinnedOffset
+ this.$element.removeClass(Affix.RESET).addClass('affix')
+ var scrollTop = this.$target.scrollTop()
+ var position = this.$element.offset()
+ return (this.pinnedOffset = position.top - scrollTop)
+ }
+
+ Affix.prototype.checkPositionWithEventLoop = function () {
+ setTimeout($.proxy(this.checkPosition, this), 1)
+ }
+
+ Affix.prototype.checkPosition = function () {
+ if (!this.$element.is(':visible')) return
+
+ var scrollHeight = $(document).height()
+ var scrollTop = this.$target.scrollTop()
+ var position = this.$element.offset()
+ var offset = this.options.offset
+ var offsetTop = offset.top
+ var offsetBottom = offset.bottom
+
+ if (typeof offset != 'object') offsetBottom = offsetTop = offset
+ if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
+ if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
+
+ var affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ? false :
+ offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' :
+ offsetTop != null && (scrollTop <= offsetTop) ? 'top' : false
+
+ if (this.affixed === affix) return
+ if (this.unpin != null) this.$element.css('top', '')
+
+ var affixType = 'affix' + (affix ? '-' + affix : '')
+ var e = $.Event(affixType + '.bs.affix')
+
+ this.$element.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ this.affixed = affix
+ this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
+
+ this.$element
+ .removeClass(Affix.RESET)
+ .addClass(affixType)
+ .trigger($.Event(affixType.replace('affix', 'affixed')))
+
+ if (affix == 'bottom') {
+ this.$element.offset({
+ top: scrollHeight - this.$element.height() - offsetBottom
+ })
+ }
+ }
+
+
+ // AFFIX PLUGIN DEFINITION
+ // =======================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.affix')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.affix
+
+ $.fn.affix = Plugin
+ $.fn.affix.Constructor = Affix
+
+
+ // AFFIX NO CONFLICT
+ // =================
+
+ $.fn.affix.noConflict = function () {
+ $.fn.affix = old
+ return this
+ }
+
+
+ // AFFIX DATA-API
+ // ==============
+
+ $(window).on('load', function () {
+ $('[data-spy="affix"]').each(function () {
+ var $spy = $(this)
+ var data = $spy.data()
+
+ data.offset = data.offset || {}
+
+ if (data.offsetBottom) data.offset.bottom = data.offsetBottom
+ if (data.offsetTop) data.offset.top = data.offsetTop
+
+ Plugin.call($spy, data)
+ })
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.2.0
+ * http://getbootstrap.com/javascript/#collapse
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // COLLAPSE PUBLIC CLASS DEFINITION
+ // ================================
+
+ var Collapse = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, Collapse.DEFAULTS, options)
+ this.transitioning = null
+
+ if (this.options.parent) this.$parent = $(this.options.parent)
+ if (this.options.toggle) this.toggle()
+ }
+
+ Collapse.VERSION = '3.2.0'
+
+ Collapse.DEFAULTS = {
+ toggle: true
+ }
+
+ Collapse.prototype.dimension = function () {
+ var hasWidth = this.$element.hasClass('width')
+ return hasWidth ? 'width' : 'height'
+ }
+
+ Collapse.prototype.show = function () {
+ if (this.transitioning || this.$element.hasClass('in')) return
+
+ var startEvent = $.Event('show.bs.collapse')
+ this.$element.trigger(startEvent)
+ if (startEvent.isDefaultPrevented()) return
+
+ var actives = this.$parent && this.$parent.find('> .panel > .in')
+
+ if (actives && actives.length) {
+ var hasData = actives.data('bs.collapse')
+ if (hasData && hasData.transitioning) return
+ Plugin.call(actives, 'hide')
+ hasData || actives.data('bs.collapse', null)
+ }
+
+ var dimension = this.dimension()
+
+ this.$element
+ .removeClass('collapse')
+ .addClass('collapsing')[dimension](0)
+
+ this.transitioning = 1
+
+ var complete = function () {
+ this.$element
+ .removeClass('collapsing')
+ .addClass('collapse in')[dimension]('')
+ this.transitioning = 0
+ this.$element
+ .trigger('shown.bs.collapse')
+ }
+
+ if (!$.support.transition) return complete.call(this)
+
+ var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+ this.$element
+ .one('bsTransitionEnd', $.proxy(complete, this))
+ .emulateTransitionEnd(350)[dimension](this.$element[0][scrollSize])
+ }
+
+ Collapse.prototype.hide = function () {
+ if (this.transitioning || !this.$element.hasClass('in')) return
+
+ var startEvent = $.Event('hide.bs.collapse')
+ this.$element.trigger(startEvent)
+ if (startEvent.isDefaultPrevented()) return
+
+ var dimension = this.dimension()
+
+ this.$element[dimension](this.$element[dimension]())[0].offsetHeight
+
+ this.$element
+ .addClass('collapsing')
+ .removeClass('collapse')
+ .removeClass('in')
+
+ this.transitioning = 1
+
+ var complete = function () {
+ this.transitioning = 0
+ this.$element
+ .trigger('hidden.bs.collapse')
+ .removeClass('collapsing')
+ .addClass('collapse')
+ }
+
+ if (!$.support.transition) return complete.call(this)
+
+ this.$element
+ [dimension](0)
+ .one('bsTransitionEnd', $.proxy(complete, this))
+ .emulateTransitionEnd(350)
+ }
+
+ Collapse.prototype.toggle = function () {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']()
+ }
+
+
+ // COLLAPSE PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.collapse')
+ var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+ if (!data && options.toggle && option == 'show') option = !option
+ if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.collapse
+
+ $.fn.collapse = Plugin
+ $.fn.collapse.Constructor = Collapse
+
+
+ // COLLAPSE NO CONFLICT
+ // ====================
+
+ $.fn.collapse.noConflict = function () {
+ $.fn.collapse = old
+ return this
+ }
+
+
+ // COLLAPSE DATA-API
+ // =================
+
+ $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
+ var href
+ var $this = $(this)
+ var target = $this.attr('data-target')
+ || e.preventDefault()
+ || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
+ var $target = $(target)
+ var data = $target.data('bs.collapse')
+ var option = data ? 'toggle' : $this.data()
+ var parent = $this.attr('data-parent')
+ var $parent = parent && $(parent)
+
+ if (!data || !data.transitioning) {
+ if ($parent) $parent.find('[data-toggle="collapse"][data-parent="' + parent + '"]').not($this).addClass('collapsed')
+ $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+ }
+
+ Plugin.call($target, option)
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.2.0
+ * http://getbootstrap.com/javascript/#scrollspy
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // SCROLLSPY CLASS DEFINITION
+ // ==========================
+
+ function ScrollSpy(element, options) {
+ var process = $.proxy(this.process, this)
+
+ this.$body = $('body')
+ this.$scrollElement = $(element).is('body') ? $(window) : $(element)
+ this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
+ this.selector = (this.options.target || '') + ' .nav li > a'
+ this.offsets = []
+ this.targets = []
+ this.activeTarget = null
+ this.scrollHeight = 0
+
+ this.$scrollElement.on('scroll.bs.scrollspy', process)
+ this.refresh()
+ this.process()
+ }
+
+ ScrollSpy.VERSION = '3.2.0'
+
+ ScrollSpy.DEFAULTS = {
+ offset: 10
+ }
+
+ ScrollSpy.prototype.getScrollHeight = function () {
+ return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
+ }
+
+ ScrollSpy.prototype.refresh = function () {
+ var offsetMethod = 'offset'
+ var offsetBase = 0
+
+ if (!$.isWindow(this.$scrollElement[0])) {
+ offsetMethod = 'position'
+ offsetBase = this.$scrollElement.scrollTop()
+ }
+
+ this.offsets = []
+ this.targets = []
+ this.scrollHeight = this.getScrollHeight()
+
+ var self = this
+
+ this.$body
+ .find(this.selector)
+ .map(function () {
+ var $el = $(this)
+ var href = $el.data('target') || $el.attr('href')
+ var $href = /^#./.test(href) && $(href)
+
+ return ($href
+ && $href.length
+ && $href.is(':visible')
+ && [[$href[offsetMethod]().top + offsetBase, href]]) || null
+ })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .each(function () {
+ self.offsets.push(this[0])
+ self.targets.push(this[1])
+ })
+ }
+
+ ScrollSpy.prototype.process = function () {
+ var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+ var scrollHeight = this.getScrollHeight()
+ var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()
+ var offsets = this.offsets
+ var targets = this.targets
+ var activeTarget = this.activeTarget
+ var i
+
+ if (this.scrollHeight != scrollHeight) {
+ this.refresh()
+ }
+
+ if (scrollTop >= maxScroll) {
+ return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
+ }
+
+ if (activeTarget && scrollTop <= offsets[0]) {
+ return activeTarget != (i = targets[0]) && this.activate(i)
+ }
+
+ for (i = offsets.length; i--;) {
+ activeTarget != targets[i]
+ && scrollTop >= offsets[i]
+ && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+ && this.activate(targets[i])
+ }
+ }
+
+ ScrollSpy.prototype.activate = function (target) {
+ this.activeTarget = target
+
+ $(this.selector)
+ .parentsUntil(this.options.target, '.active')
+ .removeClass('active')
+
+ var selector = this.selector +
+ '[data-target="' + target + '"],' +
+ this.selector + '[href="' + target + '"]'
+
+ var active = $(selector)
+ .parents('li')
+ .addClass('active')
+
+ if (active.parent('.dropdown-menu').length) {
+ active = active
+ .closest('li.dropdown')
+ .addClass('active')
+ }
+
+ active.trigger('activate.bs.scrollspy')
+ }
+
+
+ // SCROLLSPY PLUGIN DEFINITION
+ // ===========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.scrollspy')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.scrollspy
+
+ $.fn.scrollspy = Plugin
+ $.fn.scrollspy.Constructor = ScrollSpy
+
+
+ // SCROLLSPY NO CONFLICT
+ // =====================
+
+ $.fn.scrollspy.noConflict = function () {
+ $.fn.scrollspy = old
+ return this
+ }
+
+
+ // SCROLLSPY DATA-API
+ // ==================
+
+ $(window).on('load.bs.scrollspy.data-api', function () {
+ $('[data-spy="scroll"]').each(function () {
+ var $spy = $(this)
+ Plugin.call($spy, $spy.data())
+ })
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.2.0
+ * http://getbootstrap.com/javascript/#transitions
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+ // ============================================================
+
+ function transitionEnd() {
+ var el = document.createElement('bootstrap')
+
+ var transEndEventNames = {
+ WebkitTransition : 'webkitTransitionEnd',
+ MozTransition : 'transitionend',
+ OTransition : 'oTransitionEnd otransitionend',
+ transition : 'transitionend'
+ }
+
+ for (var name in transEndEventNames) {
+ if (el.style[name] !== undefined) {
+ return { end: transEndEventNames[name] }
+ }
+ }
+
+ return false // explicit for ie8 ( ._.)
+ }
+
+ // http://blog.alexmaccaw.com/css-transitions
+ $.fn.emulateTransitionEnd = function (duration) {
+ var called = false
+ var $el = this
+ $(this).one('bsTransitionEnd', function () { called = true })
+ var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+ setTimeout(callback, duration)
+ return this
+ }
+
+ $(function () {
+ $.support.transition = transitionEnd()
+
+ if (!$.support.transition) return
+
+ $.event.special.bsTransitionEnd = {
+ bindType: $.support.transition.end,
+ delegateType: $.support.transition.end,
+ handle: function (e) {
+ if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
+ }
+ }
+ })
+
+}(jQuery);
diff --git a/tools/infra-dashboard/js/fullcalendar.js b/tools/infra-dashboard/js/fullcalendar.js
new file mode 100644
index 00000000..958ca245
--- /dev/null
+++ b/tools/infra-dashboard/js/fullcalendar.js
@@ -0,0 +1,12670 @@
+/*!
+ * FullCalendar v2.7.2
+ * Docs & License: http://fullcalendar.io/
+ * (c) 2016 Adam Shaw
+ */
+
+(function(factory) {
+ if (typeof define === 'function' && define.amd) {
+ define([ 'jquery', 'moment' ], factory);
+ }
+ else if (typeof exports === 'object') { // Node/CommonJS
+ module.exports = factory(require('jquery'), require('moment'));
+ }
+ else {
+ factory(jQuery, moment);
+ }
+})(function($, moment) {
+
+;;
+
+var FC = $.fullCalendar = {
+ version: "2.7.2",
+ internalApiVersion: 3
+};
+var fcViews = FC.views = {};
+
+
+$.fn.fullCalendar = function(options) {
+ var args = Array.prototype.slice.call(arguments, 1); // for a possible method call
+ var res = this; // what this function will return (this jQuery object by default)
+
+ this.each(function(i, _element) { // loop each DOM element involved
+ var element = $(_element);
+ var calendar = element.data('fullCalendar'); // get the existing calendar object (if any)
+ var singleRes; // the returned value of this single method call
+
+ // a method call
+ if (typeof options === 'string') {
+ if (calendar && $.isFunction(calendar[options])) {
+ singleRes = calendar[options].apply(calendar, args);
+ if (!i) {
+ res = singleRes; // record the first method call result
+ }
+ if (options === 'destroy') { // for the destroy method, must remove Calendar object data
+ element.removeData('fullCalendar');
+ }
+ }
+ }
+ // a new calendar initialization
+ else if (!calendar) { // don't initialize twice
+ calendar = new Calendar(element, options);
+ element.data('fullCalendar', calendar);
+ calendar.render();
+ }
+ });
+
+ return res;
+};
+
+
+var complexOptions = [ // names of options that are objects whose properties should be combined
+ 'header',
+ 'buttonText',
+ 'buttonIcons',
+ 'themeButtonIcons'
+];
+
+
+// Merges an array of option objects into a single object
+function mergeOptions(optionObjs) {
+ return mergeProps(optionObjs, complexOptions);
+}
+
+
+// Given options specified for the calendar's constructor, massages any legacy options into a non-legacy form.
+// Converts View-Option-Hashes into the View-Specific-Options format.
+function massageOverrides(input) {
+ var overrides = { views: input.views || {} }; // the output. ensure a `views` hash
+ var subObj;
+
+ // iterate through all option override properties (except `views`)
+ $.each(input, function(name, val) {
+ if (name != 'views') {
+
+ // could the value be a legacy View-Option-Hash?
+ if (
+ $.isPlainObject(val) &&
+ !/(time|duration|interval)$/i.test(name) && // exclude duration options. might be given as objects
+ $.inArray(name, complexOptions) == -1 // complex options aren't allowed to be View-Option-Hashes
+ ) {
+ subObj = null;
+
+ // iterate through the properties of this possible View-Option-Hash value
+ $.each(val, function(subName, subVal) {
+
+ // is the property targeting a view?
+ if (/^(month|week|day|default|basic(Week|Day)?|agenda(Week|Day)?)$/.test(subName)) {
+ if (!overrides.views[subName]) { // ensure the view-target entry exists
+ overrides.views[subName] = {};
+ }
+ overrides.views[subName][name] = subVal; // record the value in the `views` object
+ }
+ else { // a non-View-Option-Hash property
+ if (!subObj) {
+ subObj = {};
+ }
+ subObj[subName] = subVal; // accumulate these unrelated values for later
+ }
+ });
+
+ if (subObj) { // non-View-Option-Hash properties? transfer them as-is
+ overrides[name] = subObj;
+ }
+ }
+ else {
+ overrides[name] = val; // transfer normal options as-is
+ }
+ }
+ });
+
+ return overrides;
+}
+
+;;
+
+// exports
+FC.intersectRanges = intersectRanges;
+FC.applyAll = applyAll;
+FC.debounce = debounce;
+FC.isInt = isInt;
+FC.htmlEscape = htmlEscape;
+FC.cssToStr = cssToStr;
+FC.proxy = proxy;
+FC.capitaliseFirstLetter = capitaliseFirstLetter;
+
+
+/* FullCalendar-specific DOM Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+
+// Given the scrollbar widths of some other container, create borders/margins on rowEls in order to match the left
+// and right space that was offset by the scrollbars. A 1-pixel border first, then margin beyond that.
+function compensateScroll(rowEls, scrollbarWidths) {
+ if (scrollbarWidths.left) {
+ rowEls.css({
+ 'border-left-width': 1,
+ 'margin-left': scrollbarWidths.left - 1
+ });
+ }
+ if (scrollbarWidths.right) {
+ rowEls.css({
+ 'border-right-width': 1,
+ 'margin-right': scrollbarWidths.right - 1
+ });
+ }
+}
+
+
+// Undoes compensateScroll and restores all borders/margins
+function uncompensateScroll(rowEls) {
+ rowEls.css({
+ 'margin-left': '',
+ 'margin-right': '',
+ 'border-left-width': '',
+ 'border-right-width': ''
+ });
+}
+
+
+// Make the mouse cursor express that an event is not allowed in the current area
+function disableCursor() {
+ $('body').addClass('fc-not-allowed');
+}
+
+
+// Returns the mouse cursor to its original look
+function enableCursor() {
+ $('body').removeClass('fc-not-allowed');
+}
+
+
+// Given a total available height to fill, have `els` (essentially child rows) expand to accomodate.
+// By default, all elements that are shorter than the recommended height are expanded uniformly, not considering
+// any other els that are already too tall. if `shouldRedistribute` is on, it considers these tall rows and
+// reduces the available height.
+function distributeHeight(els, availableHeight, shouldRedistribute) {
+
+ // *FLOORING NOTE*: we floor in certain places because zoom can give inaccurate floating-point dimensions,
+ // and it is better to be shorter than taller, to avoid creating unnecessary scrollbars.
+
+ var minOffset1 = Math.floor(availableHeight / els.length); // for non-last element
+ var minOffset2 = Math.floor(availableHeight - minOffset1 * (els.length - 1)); // for last element *FLOORING NOTE*
+ var flexEls = []; // elements that are allowed to expand. array of DOM nodes
+ var flexOffsets = []; // amount of vertical space it takes up
+ var flexHeights = []; // actual css height
+ var usedHeight = 0;
+
+ undistributeHeight(els); // give all elements their natural height
+
+ // find elements that are below the recommended height (expandable).
+ // important to query for heights in a single first pass (to avoid reflow oscillation).
+ els.each(function(i, el) {
+ var minOffset = i === els.length - 1 ? minOffset2 : minOffset1;
+ var naturalOffset = $(el).outerHeight(true);
+
+ if (naturalOffset < minOffset) {
+ flexEls.push(el);
+ flexOffsets.push(naturalOffset);
+ flexHeights.push($(el).height());
+ }
+ else {
+ // this element stretches past recommended height (non-expandable). mark the space as occupied.
+ usedHeight += naturalOffset;
+ }
+ });
+
+ // readjust the recommended height to only consider the height available to non-maxed-out rows.
+ if (shouldRedistribute) {
+ availableHeight -= usedHeight;
+ minOffset1 = Math.floor(availableHeight / flexEls.length);
+ minOffset2 = Math.floor(availableHeight - minOffset1 * (flexEls.length - 1)); // *FLOORING NOTE*
+ }
+
+ // assign heights to all expandable elements
+ $(flexEls).each(function(i, el) {
+ var minOffset = i === flexEls.length - 1 ? minOffset2 : minOffset1;
+ var naturalOffset = flexOffsets[i];
+ var naturalHeight = flexHeights[i];
+ var newHeight = minOffset - (naturalOffset - naturalHeight); // subtract the margin/padding
+
+ if (naturalOffset < minOffset) { // we check this again because redistribution might have changed things
+ $(el).height(newHeight);
+ }
+ });
+}
+
+
+// Undoes distrubuteHeight, restoring all els to their natural height
+function undistributeHeight(els) {
+ els.height('');
+}
+
+
+// Given `els`, a jQuery set of <td> cells, find the cell with the largest natural width and set the widths of all the
+// cells to be that width.
+// PREREQUISITE: if you want a cell to take up width, it needs to have a single inner element w/ display:inline
+function matchCellWidths(els) {
+ var maxInnerWidth = 0;
+
+ els.find('> span').each(function(i, innerEl) {
+ var innerWidth = $(innerEl).outerWidth();
+ if (innerWidth > maxInnerWidth) {
+ maxInnerWidth = innerWidth;
+ }
+ });
+
+ maxInnerWidth++; // sometimes not accurate of width the text needs to stay on one line. insurance
+
+ els.width(maxInnerWidth);
+
+ return maxInnerWidth;
+}
+
+
+// Given one element that resides inside another,
+// Subtracts the height of the inner element from the outer element.
+function subtractInnerElHeight(outerEl, innerEl) {
+ var both = outerEl.add(innerEl);
+ var diff;
+
+ // effin' IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked
+ both.css({
+ position: 'relative', // cause a reflow, which will force fresh dimension recalculation
+ left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll
+ });
+ diff = outerEl.outerHeight() - innerEl.outerHeight(); // grab the dimensions
+ both.css({ position: '', left: '' }); // undo hack
+
+ return diff;
+}
+
+
+/* Element Geom Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+FC.getOuterRect = getOuterRect;
+FC.getClientRect = getClientRect;
+FC.getContentRect = getContentRect;
+FC.getScrollbarWidths = getScrollbarWidths;
+
+
+// borrowed from https://github.com/jquery/jquery-ui/blob/1.11.0/ui/core.js#L51
+function getScrollParent(el) {
+ var position = el.css('position'),
+ scrollParent = el.parents().filter(function() {
+ var parent = $(this);
+ return (/(auto|scroll)/).test(
+ parent.css('overflow') + parent.css('overflow-y') + parent.css('overflow-x')
+ );
+ }).eq(0);
+
+ return position === 'fixed' || !scrollParent.length ? $(el[0].ownerDocument || document) : scrollParent;
+}
+
+
+// Queries the outer bounding area of a jQuery element.
+// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
+// Origin is optional.
+function getOuterRect(el, origin) {
+ var offset = el.offset();
+ var left = offset.left - (origin ? origin.left : 0);
+ var top = offset.top - (origin ? origin.top : 0);
+
+ return {
+ left: left,
+ right: left + el.outerWidth(),
+ top: top,
+ bottom: top + el.outerHeight()
+ };
+}
+
+
+// Queries the area within the margin/border/scrollbars of a jQuery element. Does not go within the padding.
+// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
+// Origin is optional.
+// NOTE: should use clientLeft/clientTop, but very unreliable cross-browser.
+function getClientRect(el, origin) {
+ var offset = el.offset();
+ var scrollbarWidths = getScrollbarWidths(el);
+ var left = offset.left + getCssFloat(el, 'border-left-width') + scrollbarWidths.left - (origin ? origin.left : 0);
+ var top = offset.top + getCssFloat(el, 'border-top-width') + scrollbarWidths.top - (origin ? origin.top : 0);
+
+ return {
+ left: left,
+ right: left + el[0].clientWidth, // clientWidth includes padding but NOT scrollbars
+ top: top,
+ bottom: top + el[0].clientHeight // clientHeight includes padding but NOT scrollbars
+ };
+}
+
+
+// Queries the area within the margin/border/padding of a jQuery element. Assumed not to have scrollbars.
+// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
+// Origin is optional.
+function getContentRect(el, origin) {
+ var offset = el.offset(); // just outside of border, margin not included
+ var left = offset.left + getCssFloat(el, 'border-left-width') + getCssFloat(el, 'padding-left') -
+ (origin ? origin.left : 0);
+ var top = offset.top + getCssFloat(el, 'border-top-width') + getCssFloat(el, 'padding-top') -
+ (origin ? origin.top : 0);
+
+ return {
+ left: left,
+ right: left + el.width(),
+ top: top,
+ bottom: top + el.height()
+ };
+}
+
+
+// Returns the computed left/right/top/bottom scrollbar widths for the given jQuery element.
+// NOTE: should use clientLeft/clientTop, but very unreliable cross-browser.
+function getScrollbarWidths(el) {
+ var leftRightWidth = el.innerWidth() - el[0].clientWidth; // the paddings cancel out, leaving the scrollbars
+ var widths = {
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: el.innerHeight() - el[0].clientHeight // the paddings cancel out, leaving the bottom scrollbar
+ };
+
+ if (getIsLeftRtlScrollbars() && el.css('direction') == 'rtl') { // is the scrollbar on the left side?
+ widths.left = leftRightWidth;
+ }
+ else {
+ widths.right = leftRightWidth;
+ }
+
+ return widths;
+}
+
+
+// Logic for determining if, when the element is right-to-left, the scrollbar appears on the left side
+
+var _isLeftRtlScrollbars = null;
+
+function getIsLeftRtlScrollbars() { // responsible for caching the computation
+ if (_isLeftRtlScrollbars === null) {
+ _isLeftRtlScrollbars = computeIsLeftRtlScrollbars();
+ }
+ return _isLeftRtlScrollbars;
+}
+
+function computeIsLeftRtlScrollbars() { // creates an offscreen test element, then removes it
+ var el = $('<div><div/></div>')
+ .css({
+ position: 'absolute',
+ top: -1000,
+ left: 0,
+ border: 0,
+ padding: 0,
+ overflow: 'scroll',
+ direction: 'rtl'
+ })
+ .appendTo('body');
+ var innerEl = el.children();
+ var res = innerEl.offset().left > el.offset().left; // is the inner div shifted to accommodate a left scrollbar?
+ el.remove();
+ return res;
+}
+
+
+// Retrieves a jQuery element's computed CSS value as a floating-point number.
+// If the queried value is non-numeric (ex: IE can return "medium" for border width), will just return zero.
+function getCssFloat(el, prop) {
+ return parseFloat(el.css(prop)) || 0;
+}
+
+
+/* Mouse / Touch Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+FC.preventDefault = preventDefault;
+
+
+// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac)
+function isPrimaryMouseButton(ev) {
+ return ev.which == 1 && !ev.ctrlKey;
+}
+
+
+function getEvX(ev) {
+ if (ev.pageX !== undefined) {
+ return ev.pageX;
+ }
+ var touches = ev.originalEvent.touches;
+ if (touches) {
+ return touches[0].pageX;
+ }
+}
+
+
+function getEvY(ev) {
+ if (ev.pageY !== undefined) {
+ return ev.pageY;
+ }
+ var touches = ev.originalEvent.touches;
+ if (touches) {
+ return touches[0].pageY;
+ }
+}
+
+
+function getEvIsTouch(ev) {
+ return /^touch/.test(ev.type);
+}
+
+
+function preventSelection(el) {
+ el.addClass('fc-unselectable')
+ .on('selectstart', preventDefault);
+}
+
+
+// Stops a mouse/touch event from doing it's native browser action
+function preventDefault(ev) {
+ ev.preventDefault();
+}
+
+
+// attach a handler to get called when ANY scroll action happens on the page.
+// this was impossible to do with normal on/off because 'scroll' doesn't bubble.
+// http://stackoverflow.com/a/32954565/96342
+// returns `true` on success.
+function bindAnyScroll(handler) {
+ if (window.addEventListener) {
+ window.addEventListener('scroll', handler, true); // useCapture=true
+ return true;
+ }
+ return false;
+}
+
+
+// undoes bindAnyScroll. must pass in the original function.
+// returns `true` on success.
+function unbindAnyScroll(handler) {
+ if (window.removeEventListener) {
+ window.removeEventListener('scroll', handler, true); // useCapture=true
+ return true;
+ }
+ return false;
+}
+
+
+/* General Geometry Utils
+----------------------------------------------------------------------------------------------------------------------*/
+
+FC.intersectRects = intersectRects;
+
+// Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false
+function intersectRects(rect1, rect2) {
+ var res = {
+ left: Math.max(rect1.left, rect2.left),
+ right: Math.min(rect1.right, rect2.right),
+ top: Math.max(rect1.top, rect2.top),
+ bottom: Math.min(rect1.bottom, rect2.bottom)
+ };
+
+ if (res.left < res.right && res.top < res.bottom) {
+ return res;
+ }
+ return false;
+}
+
+
+// Returns a new point that will have been moved to reside within the given rectangle
+function constrainPoint(point, rect) {
+ return {
+ left: Math.min(Math.max(point.left, rect.left), rect.right),
+ top: Math.min(Math.max(point.top, rect.top), rect.bottom)
+ };
+}
+
+
+// Returns a point that is the center of the given rectangle
+function getRectCenter(rect) {
+ return {
+ left: (rect.left + rect.right) / 2,
+ top: (rect.top + rect.bottom) / 2
+ };
+}
+
+
+// Subtracts point2's coordinates from point1's coordinates, returning a delta
+function diffPoints(point1, point2) {
+ return {
+ left: point1.left - point2.left,
+ top: point1.top - point2.top
+ };
+}
+
+
+/* Object Ordering by Field
+----------------------------------------------------------------------------------------------------------------------*/
+
+FC.parseFieldSpecs = parseFieldSpecs;
+FC.compareByFieldSpecs = compareByFieldSpecs;
+FC.compareByFieldSpec = compareByFieldSpec;
+FC.flexibleCompare = flexibleCompare;
+
+
+function parseFieldSpecs(input) {
+ var specs = [];
+ var tokens = [];
+ var i, token;
+
+ if (typeof input === 'string') {
+ tokens = input.split(/\s*,\s*/);
+ }
+ else if (typeof input === 'function') {
+ tokens = [ input ];
+ }
+ else if ($.isArray(input)) {
+ tokens = input;
+ }
+
+ for (i = 0; i < tokens.length; i++) {
+ token = tokens[i];
+
+ if (typeof token === 'string') {
+ specs.push(
+ token.charAt(0) == '-' ?
+ { field: token.substring(1), order: -1 } :
+ { field: token, order: 1 }
+ );
+ }
+ else if (typeof token === 'function') {
+ specs.push({ func: token });
+ }
+ }
+
+ return specs;
+}
+
+
+function compareByFieldSpecs(obj1, obj2, fieldSpecs) {
+ var i;
+ var cmp;
+
+ for (i = 0; i < fieldSpecs.length; i++) {
+ cmp = compareByFieldSpec(obj1, obj2, fieldSpecs[i]);
+ if (cmp) {
+ return cmp;
+ }
+ }
+
+ return 0;
+}
+
+
+function compareByFieldSpec(obj1, obj2, fieldSpec) {
+ if (fieldSpec.func) {
+ return fieldSpec.func(obj1, obj2);
+ }
+ return flexibleCompare(obj1[fieldSpec.field], obj2[fieldSpec.field]) *
+ (fieldSpec.order || 1);
+}
+
+
+function flexibleCompare(a, b) {
+ if (!a && !b) {
+ return 0;
+ }
+ if (b == null) {
+ return -1;
+ }
+ if (a == null) {
+ return 1;
+ }
+ if ($.type(a) === 'string' || $.type(b) === 'string') {
+ return String(a).localeCompare(String(b));
+ }
+ return a - b;
+}
+
+
+/* FullCalendar-specific Misc Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+
+// Computes the intersection of the two ranges. Returns undefined if no intersection.
+// Expects all dates to be normalized to the same timezone beforehand.
+// TODO: move to date section?
+function intersectRanges(subjectRange, constraintRange) {
+ var subjectStart = subjectRange.start;
+ var subjectEnd = subjectRange.end;
+ var constraintStart = constraintRange.start;
+ var constraintEnd = constraintRange.end;
+ var segStart, segEnd;
+ var isStart, isEnd;
+
+ if (subjectEnd > constraintStart && subjectStart < constraintEnd) { // in bounds at all?
+
+ if (subjectStart >= constraintStart) {
+ segStart = subjectStart.clone();
+ isStart = true;
+ }
+ else {
+ segStart = constraintStart.clone();
+ isStart = false;
+ }
+
+ if (subjectEnd <= constraintEnd) {
+ segEnd = subjectEnd.clone();
+ isEnd = true;
+ }
+ else {
+ segEnd = constraintEnd.clone();
+ isEnd = false;
+ }
+
+ return {
+ start: segStart,
+ end: segEnd,
+ isStart: isStart,
+ isEnd: isEnd
+ };
+ }
+}
+
+
+/* Date Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+FC.computeIntervalUnit = computeIntervalUnit;
+FC.divideRangeByDuration = divideRangeByDuration;
+FC.divideDurationByDuration = divideDurationByDuration;
+FC.multiplyDuration = multiplyDuration;
+FC.durationHasTime = durationHasTime;
+
+var dayIDs = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ];
+var intervalUnits = [ 'year', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond' ];
+
+
+// Diffs the two moments into a Duration where full-days are recorded first, then the remaining time.
+// Moments will have their timezones normalized.
+function diffDayTime(a, b) {
+ return moment.duration({
+ days: a.clone().stripTime().diff(b.clone().stripTime(), 'days'),
+ ms: a.time() - b.time() // time-of-day from day start. disregards timezone
+ });
+}
+
+
+// Diffs the two moments via their start-of-day (regardless of timezone). Produces whole-day durations.
+function diffDay(a, b) {
+ return moment.duration({
+ days: a.clone().stripTime().diff(b.clone().stripTime(), 'days')
+ });
+}
+
+
+// Diffs two moments, producing a duration, made of a whole-unit-increment of the given unit. Uses rounding.
+function diffByUnit(a, b, unit) {
+ return moment.duration(
+ Math.round(a.diff(b, unit, true)), // returnFloat=true
+ unit
+ );
+}
+
+
+// Computes the unit name of the largest whole-unit period of time.
+// For example, 48 hours will be "days" whereas 49 hours will be "hours".
+// Accepts start/end, a range object, or an original duration object.
+function computeIntervalUnit(start, end) {
+ var i, unit;
+ var val;
+
+ for (i = 0; i < intervalUnits.length; i++) {
+ unit = intervalUnits[i];
+ val = computeRangeAs(unit, start, end);
+
+ if (val >= 1 && isInt(val)) {
+ break;
+ }
+ }
+
+ return unit; // will be "milliseconds" if nothing else matches
+}
+
+
+// Computes the number of units (like "hours") in the given range.
+// Range can be a {start,end} object, separate start/end args, or a Duration.
+// Results are based on Moment's .as() and .diff() methods, so results can depend on internal handling
+// of month-diffing logic (which tends to vary from version to version).
+function computeRangeAs(unit, start, end) {
+
+ if (end != null) { // given start, end
+ return end.diff(start, unit, true);
+ }
+ else if (moment.isDuration(start)) { // given duration
+ return start.as(unit);
+ }
+ else { // given { start, end } range object
+ return start.end.diff(start.start, unit, true);
+ }
+}
+
+
+// Intelligently divides a range (specified by a start/end params) by a duration
+function divideRangeByDuration(start, end, dur) {
+ var months;
+
+ if (durationHasTime(dur)) {
+ return (end - start) / dur;
+ }
+ months = dur.asMonths();
+ if (Math.abs(months) >= 1 && isInt(months)) {
+ return end.diff(start, 'months', true) / months;
+ }
+ return end.diff(start, 'days', true) / dur.asDays();
+}
+
+
+// Intelligently divides one duration by another
+function divideDurationByDuration(dur1, dur2) {
+ var months1, months2;
+
+ if (durationHasTime(dur1) || durationHasTime(dur2)) {
+ return dur1 / dur2;
+ }
+ months1 = dur1.asMonths();
+ months2 = dur2.asMonths();
+ if (
+ Math.abs(months1) >= 1 && isInt(months1) &&
+ Math.abs(months2) >= 1 && isInt(months2)
+ ) {
+ return months1 / months2;
+ }
+ return dur1.asDays() / dur2.asDays();
+}
+
+
+// Intelligently multiplies a duration by a number
+function multiplyDuration(dur, n) {
+ var months;
+
+ if (durationHasTime(dur)) {
+ return moment.duration(dur * n);
+ }
+ months = dur.asMonths();
+ if (Math.abs(months) >= 1 && isInt(months)) {
+ return moment.duration({ months: months * n });
+ }
+ return moment.duration({ days: dur.asDays() * n });
+}
+
+
+// Returns a boolean about whether the given duration has any time parts (hours/minutes/seconds/ms)
+function durationHasTime(dur) {
+ return Boolean(dur.hours() || dur.minutes() || dur.seconds() || dur.milliseconds());
+}
+
+
+function isNativeDate(input) {
+ return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date;
+}
+
+
+// Returns a boolean about whether the given input is a time string, like "06:40:00" or "06:00"
+function isTimeString(str) {
+ return /^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(str);
+}
+
+
+/* Logging and Debug
+----------------------------------------------------------------------------------------------------------------------*/
+
+FC.log = function() {
+ var console = window.console;
+
+ if (console && console.log) {
+ return console.log.apply(console, arguments);
+ }
+};
+
+FC.warn = function() {
+ var console = window.console;
+
+ if (console && console.warn) {
+ return console.warn.apply(console, arguments);
+ }
+ else {
+ return FC.log.apply(FC, arguments);
+ }
+};
+
+
+/* General Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+var hasOwnPropMethod = {}.hasOwnProperty;
+
+
+// Merges an array of objects into a single object.
+// The second argument allows for an array of property names who's object values will be merged together.
+function mergeProps(propObjs, complexProps) {
+ var dest = {};
+ var i, name;
+ var complexObjs;
+ var j, val;
+ var props;
+
+ if (complexProps) {
+ for (i = 0; i < complexProps.length; i++) {
+ name = complexProps[i];
+ complexObjs = [];
+
+ // collect the trailing object values, stopping when a non-object is discovered
+ for (j = propObjs.length - 1; j >= 0; j--) {
+ val = propObjs[j][name];
+
+ if (typeof val === 'object') {
+ complexObjs.unshift(val);
+ }
+ else if (val !== undefined) {
+ dest[name] = val; // if there were no objects, this value will be used
+ break;
+ }
+ }
+
+ // if the trailing values were objects, use the merged value
+ if (complexObjs.length) {
+ dest[name] = mergeProps(complexObjs);
+ }
+ }
+ }
+
+ // copy values into the destination, going from last to first
+ for (i = propObjs.length - 1; i >= 0; i--) {
+ props = propObjs[i];
+
+ for (name in props) {
+ if (!(name in dest)) { // if already assigned by previous props or complex props, don't reassign
+ dest[name] = props[name];
+ }
+ }
+ }
+
+ return dest;
+}
+
+
+// Create an object that has the given prototype. Just like Object.create
+function createObject(proto) {
+ var f = function() {};
+ f.prototype = proto;
+ return new f();
+}
+
+
+function copyOwnProps(src, dest) {
+ for (var name in src) {
+ if (hasOwnProp(src, name)) {
+ dest[name] = src[name];
+ }
+ }
+}
+
+
+// Copies over certain methods with the same names as Object.prototype methods. Overcomes an IE<=8 bug:
+// https://developer.mozilla.org/en-US/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug
+function copyNativeMethods(src, dest) {
+ var names = [ 'constructor', 'toString', 'valueOf' ];
+ var i, name;
+
+ for (i = 0; i < names.length; i++) {
+ name = names[i];
+
+ if (src[name] !== Object.prototype[name]) {
+ dest[name] = src[name];
+ }
+ }
+}
+
+
+function hasOwnProp(obj, name) {
+ return hasOwnPropMethod.call(obj, name);
+}
+
+
+// Is the given value a non-object non-function value?
+function isAtomic(val) {
+ return /undefined|null|boolean|number|string/.test($.type(val));
+}
+
+
+function applyAll(functions, thisObj, args) {
+ if ($.isFunction(functions)) {
+ functions = [ functions ];
+ }
+ if (functions) {
+ var i;
+ var ret;
+ for (i=0; i<functions.length; i++) {
+ ret = functions[i].apply(thisObj, args) || ret;
+ }
+ return ret;
+ }
+}
+
+
+function firstDefined() {
+ for (var i=0; i<arguments.length; i++) {
+ if (arguments[i] !== undefined) {
+ return arguments[i];
+ }
+ }
+}
+
+
+function htmlEscape(s) {
+ return (s + '').replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/'/g, '&#039;')
+ .replace(/"/g, '&quot;')
+ .replace(/\n/g, '<br />');
+}
+
+
+function stripHtmlEntities(text) {
+ return text.replace(/&.*?;/g, '');
+}
+
+
+// Given a hash of CSS properties, returns a string of CSS.
+// Uses property names as-is (no camel-case conversion). Will not make statements for null/undefined values.
+function cssToStr(cssProps) {
+ var statements = [];
+
+ $.each(cssProps, function(name, val) {
+ if (val != null) {
+ statements.push(name + ':' + val);
+ }
+ });
+
+ return statements.join(';');
+}
+
+
+function capitaliseFirstLetter(str) {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+}
+
+
+function compareNumbers(a, b) { // for .sort()
+ return a - b;
+}
+
+
+function isInt(n) {
+ return n % 1 === 0;
+}
+
+
+// Returns a method bound to the given object context.
+// Just like one of the jQuery.proxy signatures, but without the undesired behavior of treating the same method with
+// different contexts as identical when binding/unbinding events.
+function proxy(obj, methodName) {
+ var method = obj[methodName];
+
+ return function() {
+ return method.apply(obj, arguments);
+ };
+}
+
+
+// Returns a function, that, as long as it continues to be invoked, will not
+// be triggered. The function will be called after it stops being called for
+// N milliseconds. If `immediate` is passed, trigger the function on the
+// leading edge, instead of the trailing.
+// https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714
+function debounce(func, wait, immediate) {
+ var timeout, args, context, timestamp, result;
+
+ var later = function() {
+ var last = +new Date() - timestamp;
+ if (last < wait) {
+ timeout = setTimeout(later, wait - last);
+ }
+ else {
+ timeout = null;
+ if (!immediate) {
+ result = func.apply(context, args);
+ context = args = null;
+ }
+ }
+ };
+
+ return function() {
+ context = this;
+ args = arguments;
+ timestamp = +new Date();
+ var callNow = immediate && !timeout;
+ if (!timeout) {
+ timeout = setTimeout(later, wait);
+ }
+ if (callNow) {
+ result = func.apply(context, args);
+ context = args = null;
+ }
+ return result;
+ };
+}
+
+;;
+
+var ambigDateOfMonthRegex = /^\s*\d{4}-\d\d$/;
+var ambigTimeOrZoneRegex =
+ /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/;
+var newMomentProto = moment.fn; // where we will attach our new methods
+var oldMomentProto = $.extend({}, newMomentProto); // copy of original moment methods
+var allowValueOptimization;
+var setUTCValues; // function defined below
+var setLocalValues; // function defined below
+
+
+// Creating
+// -------------------------------------------------------------------------------------------------
+
+// Creates a new moment, similar to the vanilla moment(...) constructor, but with
+// extra features (ambiguous time, enhanced formatting). When given an existing moment,
+// it will function as a clone (and retain the zone of the moment). Anything else will
+// result in a moment in the local zone.
+FC.moment = function() {
+ return makeMoment(arguments);
+};
+
+// Sames as FC.moment, but forces the resulting moment to be in the UTC timezone.
+FC.moment.utc = function() {
+ var mom = makeMoment(arguments, true);
+
+ // Force it into UTC because makeMoment doesn't guarantee it
+ // (if given a pre-existing moment for example)
+ if (mom.hasTime()) { // don't give ambiguously-timed moments a UTC zone
+ mom.utc();
+ }
+
+ return mom;
+};
+
+// Same as FC.moment, but when given an ISO8601 string, the timezone offset is preserved.
+// ISO8601 strings with no timezone offset will become ambiguously zoned.
+FC.moment.parseZone = function() {
+ return makeMoment(arguments, true, true);
+};
+
+// Builds an enhanced moment from args. When given an existing moment, it clones. When given a
+// native Date, or called with no arguments (the current time), the resulting moment will be local.
+// Anything else needs to be "parsed" (a string or an array), and will be affected by:
+// parseAsUTC - if there is no zone information, should we parse the input in UTC?
+// parseZone - if there is zone information, should we force the zone of the moment?
+function makeMoment(args, parseAsUTC, parseZone) {
+ var input = args[0];
+ var isSingleString = args.length == 1 && typeof input === 'string';
+ var isAmbigTime;
+ var isAmbigZone;
+ var ambigMatch;
+ var mom;
+
+ if (moment.isMoment(input)) {
+ mom = moment.apply(null, args); // clone it
+ transferAmbigs(input, mom); // the ambig flags weren't transfered with the clone
+ }
+ else if (isNativeDate(input) || input === undefined) {
+ mom = moment.apply(null, args); // will be local
+ }
+ else { // "parsing" is required
+ isAmbigTime = false;
+ isAmbigZone = false;
+
+ if (isSingleString) {
+ if (ambigDateOfMonthRegex.test(input)) {
+ // accept strings like '2014-05', but convert to the first of the month
+ input += '-01';
+ args = [ input ]; // for when we pass it on to moment's constructor
+ isAmbigTime = true;
+ isAmbigZone = true;
+ }
+ else if ((ambigMatch = ambigTimeOrZoneRegex.exec(input))) {
+ isAmbigTime = !ambigMatch[5]; // no time part?
+ isAmbigZone = true;
+ }
+ }
+ else if ($.isArray(input)) {
+ // arrays have no timezone information, so assume ambiguous zone
+ isAmbigZone = true;
+ }
+ // otherwise, probably a string with a format
+
+ if (parseAsUTC || isAmbigTime) {
+ mom = moment.utc.apply(moment, args);
+ }
+ else {
+ mom = moment.apply(null, args);
+ }
+
+ if (isAmbigTime) {
+ mom._ambigTime = true;
+ mom._ambigZone = true; // ambiguous time always means ambiguous zone
+ }
+ else if (parseZone) { // let's record the inputted zone somehow
+ if (isAmbigZone) {
+ mom._ambigZone = true;
+ }
+ else if (isSingleString) {
+ if (mom.utcOffset) {
+ mom.utcOffset(input); // if not a valid zone, will assign UTC
+ }
+ else {
+ mom.zone(input); // for moment-pre-2.9
+ }
+ }
+ }
+ }
+
+ mom._fullCalendar = true; // flag for extended functionality
+
+ return mom;
+}
+
+
+// A clone method that works with the flags related to our enhanced functionality.
+// In the future, use moment.momentProperties
+newMomentProto.clone = function() {
+ var mom = oldMomentProto.clone.apply(this, arguments);
+
+ // these flags weren't transfered with the clone
+ transferAmbigs(this, mom);
+ if (this._fullCalendar) {
+ mom._fullCalendar = true;
+ }
+
+ return mom;
+};
+
+
+// Week Number
+// -------------------------------------------------------------------------------------------------
+
+
+// Returns the week number, considering the locale's custom week number calcuation
+// `weeks` is an alias for `week`
+newMomentProto.week = newMomentProto.weeks = function(input) {
+ var weekCalc = (this._locale || this._lang) // works pre-moment-2.8
+ ._fullCalendar_weekCalc;
+
+ if (input == null && typeof weekCalc === 'function') { // custom function only works for getter
+ return weekCalc(this);
+ }
+ else if (weekCalc === 'ISO') {
+ return oldMomentProto.isoWeek.apply(this, arguments); // ISO getter/setter
+ }
+
+ return oldMomentProto.week.apply(this, arguments); // local getter/setter
+};
+
+
+// Time-of-day
+// -------------------------------------------------------------------------------------------------
+
+// GETTER
+// Returns a Duration with the hours/minutes/seconds/ms values of the moment.
+// If the moment has an ambiguous time, a duration of 00:00 will be returned.
+//
+// SETTER
+// You can supply a Duration, a Moment, or a Duration-like argument.
+// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous.
+newMomentProto.time = function(time) {
+
+ // Fallback to the original method (if there is one) if this moment wasn't created via FullCalendar.
+ // `time` is a generic enough method name where this precaution is necessary to avoid collisions w/ other plugins.
+ if (!this._fullCalendar) {
+ return oldMomentProto.time.apply(this, arguments);
+ }
+
+ if (time == null) { // getter
+ return moment.duration({
+ hours: this.hours(),
+ minutes: this.minutes(),
+ seconds: this.seconds(),
+ milliseconds: this.milliseconds()
+ });
+ }
+ else { // setter
+
+ this._ambigTime = false; // mark that the moment now has a time
+
+ if (!moment.isDuration(time) && !moment.isMoment(time)) {
+ time = moment.duration(time);
+ }
+
+ // The day value should cause overflow (so 24 hours becomes 00:00:00 of next day).
+ // Only for Duration times, not Moment times.
+ var dayHours = 0;
+ if (moment.isDuration(time)) {
+ dayHours = Math.floor(time.asDays()) * 24;
+ }
+
+ // We need to set the individual fields.
+ // Can't use startOf('day') then add duration. In case of DST at start of day.
+ return this.hours(dayHours + time.hours())
+ .minutes(time.minutes())
+ .seconds(time.seconds())
+ .milliseconds(time.milliseconds());
+ }
+};
+
+// Converts the moment to UTC, stripping out its time-of-day and timezone offset,
+// but preserving its YMD. A moment with a stripped time will display no time
+// nor timezone offset when .format() is called.
+newMomentProto.stripTime = function() {
+ var a;
+
+ if (!this._ambigTime) {
+
+ // get the values before any conversion happens
+ a = this.toArray(); // array of y/m/d/h/m/s/ms
+
+ // TODO: use keepLocalTime in the future
+ this.utc(); // set the internal UTC flag (will clear the ambig flags)
+ setUTCValues(this, a.slice(0, 3)); // set the year/month/date. time will be zero
+
+ // Mark the time as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(),
+ // which clears all ambig flags. Same with setUTCValues with moment-timezone.
+ this._ambigTime = true;
+ this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
+ }
+
+ return this; // for chaining
+};
+
+// Returns if the moment has a non-ambiguous time (boolean)
+newMomentProto.hasTime = function() {
+ return !this._ambigTime;
+};
+
+
+// Timezone
+// -------------------------------------------------------------------------------------------------
+
+// Converts the moment to UTC, stripping out its timezone offset, but preserving its
+// YMD and time-of-day. A moment with a stripped timezone offset will display no
+// timezone offset when .format() is called.
+// TODO: look into Moment's keepLocalTime functionality
+newMomentProto.stripZone = function() {
+ var a, wasAmbigTime;
+
+ if (!this._ambigZone) {
+
+ // get the values before any conversion happens
+ a = this.toArray(); // array of y/m/d/h/m/s/ms
+ wasAmbigTime = this._ambigTime;
+
+ this.utc(); // set the internal UTC flag (might clear the ambig flags, depending on Moment internals)
+ setUTCValues(this, a); // will set the year/month/date/hours/minutes/seconds/ms
+
+ // the above call to .utc()/.utcOffset() unfortunately might clear the ambig flags, so restore
+ this._ambigTime = wasAmbigTime || false;
+
+ // Mark the zone as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(),
+ // which clears the ambig flags. Same with setUTCValues with moment-timezone.
+ this._ambigZone = true;
+ }
+
+ return this; // for chaining
+};
+
+// Returns of the moment has a non-ambiguous timezone offset (boolean)
+newMomentProto.hasZone = function() {
+ return !this._ambigZone;
+};
+
+
+// this method implicitly marks a zone
+newMomentProto.local = function() {
+ var a = this.toArray(); // year,month,date,hours,minutes,seconds,ms as an array
+ var wasAmbigZone = this._ambigZone;
+
+ oldMomentProto.local.apply(this, arguments);
+
+ // ensure non-ambiguous
+ // this probably already happened via local() -> utcOffset(), but don't rely on Moment's internals
+ this._ambigTime = false;
+ this._ambigZone = false;
+
+ if (wasAmbigZone) {
+ // If the moment was ambiguously zoned, the date fields were stored as UTC.
+ // We want to preserve these, but in local time.
+ // TODO: look into Moment's keepLocalTime functionality
+ setLocalValues(this, a);
+ }
+
+ return this; // for chaining
+};
+
+
+// implicitly marks a zone
+newMomentProto.utc = function() {
+ oldMomentProto.utc.apply(this, arguments);
+
+ // ensure non-ambiguous
+ // this probably already happened via utc() -> utcOffset(), but don't rely on Moment's internals
+ this._ambigTime = false;
+ this._ambigZone = false;
+
+ return this;
+};
+
+
+// methods for arbitrarily manipulating timezone offset.
+// should clear time/zone ambiguity when called.
+$.each([
+ 'zone', // only in moment-pre-2.9. deprecated afterwards
+ 'utcOffset'
+], function(i, name) {
+ if (oldMomentProto[name]) { // original method exists?
+
+ // this method implicitly marks a zone (will probably get called upon .utc() and .local())
+ newMomentProto[name] = function(tzo) {
+
+ if (tzo != null) { // setter
+ // these assignments needs to happen before the original zone method is called.
+ // I forget why, something to do with a browser crash.
+ this._ambigTime = false;
+ this._ambigZone = false;
+ }
+
+ return oldMomentProto[name].apply(this, arguments);
+ };
+ }
+});
+
+
+// Formatting
+// -------------------------------------------------------------------------------------------------
+
+newMomentProto.format = function() {
+ if (this._fullCalendar && arguments[0]) { // an enhanced moment? and a format string provided?
+ return formatDate(this, arguments[0]); // our extended formatting
+ }
+ if (this._ambigTime) {
+ return oldMomentFormat(this, 'YYYY-MM-DD');
+ }
+ if (this._ambigZone) {
+ return oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
+ }
+ return oldMomentProto.format.apply(this, arguments);
+};
+
+newMomentProto.toISOString = function() {
+ if (this._ambigTime) {
+ return oldMomentFormat(this, 'YYYY-MM-DD');
+ }
+ if (this._ambigZone) {
+ return oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
+ }
+ return oldMomentProto.toISOString.apply(this, arguments);
+};
+
+
+// Querying
+// -------------------------------------------------------------------------------------------------
+
+// Is the moment within the specified range? `end` is exclusive.
+// FYI, this method is not a standard Moment method, so always do our enhanced logic.
+newMomentProto.isWithin = function(start, end) {
+ var a = commonlyAmbiguate([ this, start, end ]);
+ return a[0] >= a[1] && a[0] < a[2];
+};
+
+// When isSame is called with units, timezone ambiguity is normalized before the comparison happens.
+// If no units specified, the two moments must be identically the same, with matching ambig flags.
+newMomentProto.isSame = function(input, units) {
+ var a;
+
+ // only do custom logic if this is an enhanced moment
+ if (!this._fullCalendar) {
+ return oldMomentProto.isSame.apply(this, arguments);
+ }
+
+ if (units) {
+ a = commonlyAmbiguate([ this, input ], true); // normalize timezones but don't erase times
+ return oldMomentProto.isSame.call(a[0], a[1], units);
+ }
+ else {
+ input = FC.moment.parseZone(input); // normalize input
+ return oldMomentProto.isSame.call(this, input) &&
+ Boolean(this._ambigTime) === Boolean(input._ambigTime) &&
+ Boolean(this._ambigZone) === Boolean(input._ambigZone);
+ }
+};
+
+// Make these query methods work with ambiguous moments
+$.each([
+ 'isBefore',
+ 'isAfter'
+], function(i, methodName) {
+ newMomentProto[methodName] = function(input, units) {
+ var a;
+
+ // only do custom logic if this is an enhanced moment
+ if (!this._fullCalendar) {
+ return oldMomentProto[methodName].apply(this, arguments);
+ }
+
+ a = commonlyAmbiguate([ this, input ]);
+ return oldMomentProto[methodName].call(a[0], a[1], units);
+ };
+});
+
+
+// Misc Internals
+// -------------------------------------------------------------------------------------------------
+
+// given an array of moment-like inputs, return a parallel array w/ moments similarly ambiguated.
+// for example, of one moment has ambig time, but not others, all moments will have their time stripped.
+// set `preserveTime` to `true` to keep times, but only normalize zone ambiguity.
+// returns the original moments if no modifications are necessary.
+function commonlyAmbiguate(inputs, preserveTime) {
+ var anyAmbigTime = false;
+ var anyAmbigZone = false;
+ var len = inputs.length;
+ var moms = [];
+ var i, mom;
+
+ // parse inputs into real moments and query their ambig flags
+ for (i = 0; i < len; i++) {
+ mom = inputs[i];
+ if (!moment.isMoment(mom)) {
+ mom = FC.moment.parseZone(mom);
+ }
+ anyAmbigTime = anyAmbigTime || mom._ambigTime;
+ anyAmbigZone = anyAmbigZone || mom._ambigZone;
+ moms.push(mom);
+ }
+
+ // strip each moment down to lowest common ambiguity
+ // use clones to avoid modifying the original moments
+ for (i = 0; i < len; i++) {
+ mom = moms[i];
+ if (!preserveTime && anyAmbigTime && !mom._ambigTime) {
+ moms[i] = mom.clone().stripTime();
+ }
+ else if (anyAmbigZone && !mom._ambigZone) {
+ moms[i] = mom.clone().stripZone();
+ }
+ }
+
+ return moms;
+}
+
+// Transfers all the flags related to ambiguous time/zone from the `src` moment to the `dest` moment
+// TODO: look into moment.momentProperties for this.
+function transferAmbigs(src, dest) {
+ if (src._ambigTime) {
+ dest._ambigTime = true;
+ }
+ else if (dest._ambigTime) {
+ dest._ambigTime = false;
+ }
+
+ if (src._ambigZone) {
+ dest._ambigZone = true;
+ }
+ else if (dest._ambigZone) {
+ dest._ambigZone = false;
+ }
+}
+
+
+// Sets the year/month/date/etc values of the moment from the given array.
+// Inefficient because it calls each individual setter.
+function setMomentValues(mom, a) {
+ mom.year(a[0] || 0)
+ .month(a[1] || 0)
+ .date(a[2] || 0)
+ .hours(a[3] || 0)
+ .minutes(a[4] || 0)
+ .seconds(a[5] || 0)
+ .milliseconds(a[6] || 0);
+}
+
+// Can we set the moment's internal date directly?
+allowValueOptimization = '_d' in moment() && 'updateOffset' in moment;
+
+// Utility function. Accepts a moment and an array of the UTC year/month/date/etc values to set.
+// Assumes the given moment is already in UTC mode.
+setUTCValues = allowValueOptimization ? function(mom, a) {
+ // simlate what moment's accessors do
+ mom._d.setTime(Date.UTC.apply(Date, a));
+ moment.updateOffset(mom, false); // keepTime=false
+} : setMomentValues;
+
+// Utility function. Accepts a moment and an array of the local year/month/date/etc values to set.
+// Assumes the given moment is already in local mode.
+setLocalValues = allowValueOptimization ? function(mom, a) {
+ // simlate what moment's accessors do
+ mom._d.setTime(+new Date( // FYI, there is now way to apply an array of args to a constructor
+ a[0] || 0,
+ a[1] || 0,
+ a[2] || 0,
+ a[3] || 0,
+ a[4] || 0,
+ a[5] || 0,
+ a[6] || 0
+ ));
+ moment.updateOffset(mom, false); // keepTime=false
+} : setMomentValues;
+
+;;
+
+// Single Date Formatting
+// -------------------------------------------------------------------------------------------------
+
+
+// call this if you want Moment's original format method to be used
+function oldMomentFormat(mom, formatStr) {
+ return oldMomentProto.format.call(mom, formatStr); // oldMomentProto defined in moment-ext.js
+}
+
+
+// Formats `date` with a Moment formatting string, but allow our non-zero areas and
+// additional token.
+function formatDate(date, formatStr) {
+ return formatDateWithChunks(date, getFormatStringChunks(formatStr));
+}
+
+
+function formatDateWithChunks(date, chunks) {
+ var s = '';
+ var i;
+
+ for (i=0; i<chunks.length; i++) {
+ s += formatDateWithChunk(date, chunks[i]);
+ }
+
+ return s;
+}
+
+
+// addition formatting tokens we want recognized
+var tokenOverrides = {
+ t: function(date) { // "a" or "p"
+ return oldMomentFormat(date, 'a').charAt(0);
+ },
+ T: function(date) { // "A" or "P"
+ return oldMomentFormat(date, 'A').charAt(0);
+ }
+};
+
+
+function formatDateWithChunk(date, chunk) {
+ var token;
+ var maybeStr;
+
+ if (typeof chunk === 'string') { // a literal string
+ return chunk;
+ }
+ else if ((token = chunk.token)) { // a token, like "YYYY"
+ if (tokenOverrides[token]) {
+ return tokenOverrides[token](date); // use our custom token
+ }
+ return oldMomentFormat(date, token);
+ }
+ else if (chunk.maybe) { // a grouping of other chunks that must be non-zero
+ maybeStr = formatDateWithChunks(date, chunk.maybe);
+ if (maybeStr.match(/[1-9]/)) {
+ return maybeStr;
+ }
+ }
+
+ return '';
+}
+
+
+// Date Range Formatting
+// -------------------------------------------------------------------------------------------------
+// TODO: make it work with timezone offset
+
+// Using a formatting string meant for a single date, generate a range string, like
+// "Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ.
+// If the dates are the same as far as the format string is concerned, just return a single
+// rendering of one date, without any separator.
+function formatRange(date1, date2, formatStr, separator, isRTL) {
+ var localeData;
+
+ date1 = FC.moment.parseZone(date1);
+ date2 = FC.moment.parseZone(date2);
+
+ localeData = (date1.localeData || date1.lang).call(date1); // works with moment-pre-2.8
+
+ // Expand localized format strings, like "LL" -> "MMMM D YYYY"
+ formatStr = localeData.longDateFormat(formatStr) || formatStr;
+ // BTW, this is not important for `formatDate` because it is impossible to put custom tokens
+ // or non-zero areas in Moment's localized format strings.
+
+ separator = separator || ' - ';
+
+ return formatRangeWithChunks(
+ date1,
+ date2,
+ getFormatStringChunks(formatStr),
+ separator,
+ isRTL
+ );
+}
+FC.formatRange = formatRange; // expose
+
+
+function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
+ var unzonedDate1 = date1.clone().stripZone(); // for formatSimilarChunk
+ var unzonedDate2 = date2.clone().stripZone(); // "
+ var chunkStr; // the rendering of the chunk
+ var leftI;
+ var leftStr = '';
+ var rightI;
+ var rightStr = '';
+ var middleI;
+ var middleStr1 = '';
+ var middleStr2 = '';
+ var middleStr = '';
+
+ // Start at the leftmost side of the formatting string and continue until you hit a token
+ // that is not the same between dates.
+ for (leftI=0; leftI<chunks.length; leftI++) {
+ chunkStr = formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2, chunks[leftI]);
+ if (chunkStr === false) {
+ break;
+ }
+ leftStr += chunkStr;
+ }
+
+ // Similarly, start at the rightmost side of the formatting string and move left
+ for (rightI=chunks.length-1; rightI>leftI; rightI--) {
+ chunkStr = formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2, chunks[rightI]);
+ if (chunkStr === false) {
+ break;
+ }
+ rightStr = chunkStr + rightStr;
+ }
+
+ // The area in the middle is different for both of the dates.
+ // Collect them distinctly so we can jam them together later.
+ for (middleI=leftI; middleI<=rightI; middleI++) {
+ middleStr1 += formatDateWithChunk(date1, chunks[middleI]);
+ middleStr2 += formatDateWithChunk(date2, chunks[middleI]);
+ }
+
+ if (middleStr1 || middleStr2) {
+ if (isRTL) {
+ middleStr = middleStr2 + separator + middleStr1;
+ }
+ else {
+ middleStr = middleStr1 + separator + middleStr2;
+ }
+ }
+
+ return leftStr + middleStr + rightStr;
+}
+
+
+var similarUnitMap = {
+ Y: 'year',
+ M: 'month',
+ D: 'day', // day of month
+ d: 'day', // day of week
+ // prevents a separator between anything time-related...
+ A: 'second', // AM/PM
+ a: 'second', // am/pm
+ T: 'second', // A/P
+ t: 'second', // a/p
+ H: 'second', // hour (24)
+ h: 'second', // hour (12)
+ m: 'second', // minute
+ s: 'second' // second
+};
+// TODO: week maybe?
+
+
+// Given a formatting chunk, and given that both dates are similar in the regard the
+// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`.
+function formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2, chunk) {
+ var token;
+ var unit;
+
+ if (typeof chunk === 'string') { // a literal string
+ return chunk;
+ }
+ else if ((token = chunk.token)) {
+ unit = similarUnitMap[token.charAt(0)];
+
+ // are the dates the same for this unit of measurement?
+ // use the unzoned dates for this calculation because unreliable when near DST (bug #2396)
+ if (unit && unzonedDate1.isSame(unzonedDate2, unit)) {
+ return oldMomentFormat(date1, token); // would be the same if we used `date2`
+ // BTW, don't support custom tokens
+ }
+ }
+
+ return false; // the chunk is NOT the same for the two dates
+ // BTW, don't support splitting on non-zero areas
+}
+
+
+// Chunking Utils
+// -------------------------------------------------------------------------------------------------
+
+
+var formatStringChunkCache = {};
+
+
+function getFormatStringChunks(formatStr) {
+ if (formatStr in formatStringChunkCache) {
+ return formatStringChunkCache[formatStr];
+ }
+ return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr));
+}
+
+
+// Break the formatting string into an array of chunks
+function chunkFormatString(formatStr) {
+ var chunks = [];
+ var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g; // TODO: more descrimination
+ var match;
+
+ while ((match = chunker.exec(formatStr))) {
+ if (match[1]) { // a literal string inside [ ... ]
+ chunks.push(match[1]);
+ }
+ else if (match[2]) { // non-zero formatting inside ( ... )
+ chunks.push({ maybe: chunkFormatString(match[2]) });
+ }
+ else if (match[3]) { // a formatting token
+ chunks.push({ token: match[3] });
+ }
+ else if (match[5]) { // an unenclosed literal string
+ chunks.push(match[5]);
+ }
+ }
+
+ return chunks;
+}
+
+;;
+
+FC.Class = Class; // export
+
+// Class that all other classes will inherit from
+function Class() { }
+
+
+// Called on a class to create a subclass.
+// Last argument contains instance methods. Any argument before the last are considered mixins.
+Class.extend = function() {
+ var len = arguments.length;
+ var i;
+ var members;
+
+ for (i = 0; i < len; i++) {
+ members = arguments[i];
+ if (i < len - 1) { // not the last argument?
+ mixIntoClass(this, members);
+ }
+ }
+
+ return extendClass(this, members || {}); // members will be undefined if no arguments
+};
+
+
+// Adds new member variables/methods to the class's prototype.
+// Can be called with another class, or a plain object hash containing new members.
+Class.mixin = function(members) {
+ mixIntoClass(this, members);
+};
+
+
+function extendClass(superClass, members) {
+ var subClass;
+
+ // ensure a constructor for the subclass, forwarding all arguments to the super-constructor if it doesn't exist
+ if (hasOwnProp(members, 'constructor')) {
+ subClass = members.constructor;
+ }
+ if (typeof subClass !== 'function') {
+ subClass = members.constructor = function() {
+ superClass.apply(this, arguments);
+ };
+ }
+
+ // build the base prototype for the subclass, which is an new object chained to the superclass's prototype
+ subClass.prototype = createObject(superClass.prototype);
+
+ // copy each member variable/method onto the the subclass's prototype
+ copyOwnProps(members, subClass.prototype);
+ copyNativeMethods(members, subClass.prototype); // hack for IE8
+
+ // copy over all class variables/methods to the subclass, such as `extend` and `mixin`
+ copyOwnProps(superClass, subClass);
+
+ return subClass;
+}
+
+
+function mixIntoClass(theClass, members) {
+ copyOwnProps(members, theClass.prototype); // TODO: copyNativeMethods?
+}
+;;
+
+var EmitterMixin = FC.EmitterMixin = {
+
+ callbackHash: null,
+
+
+ on: function(name, callback) {
+ this.loopCallbacks(name, 'add', [ callback ]);
+
+ return this; // for chaining
+ },
+
+
+ off: function(name, callback) {
+ this.loopCallbacks(name, 'remove', [ callback ]);
+
+ return this; // for chaining
+ },
+
+
+ trigger: function(name) { // args...
+ var args = Array.prototype.slice.call(arguments, 1);
+
+ this.triggerWith(name, this, args);
+
+ return this; // for chaining
+ },
+
+
+ triggerWith: function(name, context, args) {
+ this.loopCallbacks(name, 'fireWith', [ context, args ]);
+
+ return this; // for chaining
+ },
+
+
+ /*
+ Given an event name string with possible namespaces,
+ call the given methodName on all the internal Callback object with the given arguments.
+ */
+ loopCallbacks: function(name, methodName, args) {
+ var parts = name.split('.'); // "click.namespace" -> [ "click", "namespace" ]
+ var i, part;
+ var callbackObj;
+
+ for (i = 0; i < parts.length; i++) {
+ part = parts[i];
+ if (part) { // in case no event name like "click"
+ callbackObj = this.ensureCallbackObj((i ? '.' : '') + part); // put periods in front of namespaces
+ callbackObj[methodName].apply(callbackObj, args);
+ }
+ }
+ },
+
+
+ ensureCallbackObj: function(name) {
+ if (!this.callbackHash) {
+ this.callbackHash = {};
+ }
+ if (!this.callbackHash[name]) {
+ this.callbackHash[name] = $.Callbacks();
+ }
+ return this.callbackHash[name];
+ }
+
+};
+;;
+
+/*
+Utility methods for easily listening to events on another object,
+and more importantly, easily unlistening from them.
+*/
+var ListenerMixin = FC.ListenerMixin = (function() {
+ var guid = 0;
+ var ListenerMixin = {
+
+ listenerId: null,
+
+ /*
+ Given an `other` object that has on/off methods, bind the given `callback` to an event by the given name.
+ The `callback` will be called with the `this` context of the object that .listenTo is being called on.
+ Can be called:
+ .listenTo(other, eventName, callback)
+ OR
+ .listenTo(other, {
+ eventName1: callback1,
+ eventName2: callback2
+ })
+ */
+ listenTo: function(other, arg, callback) {
+ if (typeof arg === 'object') { // given dictionary of callbacks
+ for (var eventName in arg) {
+ if (arg.hasOwnProperty(eventName)) {
+ this.listenTo(other, eventName, arg[eventName]);
+ }
+ }
+ }
+ else if (typeof arg === 'string') {
+ other.on(
+ arg + '.' + this.getListenerNamespace(), // use event namespacing to identify this object
+ $.proxy(callback, this) // always use `this` context
+ // the usually-undesired jQuery guid behavior doesn't matter,
+ // because we always unbind via namespace
+ );
+ }
+ },
+
+ /*
+ Causes the current object to stop listening to events on the `other` object.
+ `eventName` is optional. If omitted, will stop listening to ALL events on `other`.
+ */
+ stopListeningTo: function(other, eventName) {
+ other.off((eventName || '') + '.' + this.getListenerNamespace());
+ },
+
+ /*
+ Returns a string, unique to this object, to be used for event namespacing
+ */
+ getListenerNamespace: function() {
+ if (this.listenerId == null) {
+ this.listenerId = guid++;
+ }
+ return '_listener' + this.listenerId;
+ }
+
+ };
+ return ListenerMixin;
+})();
+;;
+
+// simple class for toggle a `isIgnoringMouse` flag on delay
+// initMouseIgnoring must first be called, with a millisecond delay setting.
+var MouseIgnorerMixin = {
+
+ isIgnoringMouse: false, // bool
+ delayUnignoreMouse: null, // method
+
+
+ initMouseIgnoring: function(delay) {
+ this.delayUnignoreMouse = debounce(proxy(this, 'unignoreMouse'), delay || 1000);
+ },
+
+
+ // temporarily ignore mouse actions on segments
+ tempIgnoreMouse: function() {
+ this.isIgnoringMouse = true;
+ this.delayUnignoreMouse();
+ },
+
+
+ // delayUnignoreMouse eventually calls this
+ unignoreMouse: function() {
+ this.isIgnoringMouse = false;
+ }
+
+};
+
+;;
+
+/* A rectangular panel that is absolutely positioned over other content
+------------------------------------------------------------------------------------------------------------------------
+Options:
+ - className (string)
+ - content (HTML string or jQuery element set)
+ - parentEl
+ - top
+ - left
+ - right (the x coord of where the right edge should be. not a "CSS" right)
+ - autoHide (boolean)
+ - show (callback)
+ - hide (callback)
+*/
+
+var Popover = Class.extend(ListenerMixin, {
+
+ isHidden: true,
+ options: null,
+ el: null, // the container element for the popover. generated by this object
+ margin: 10, // the space required between the popover and the edges of the scroll container
+
+
+ constructor: function(options) {
+ this.options = options || {};
+ },
+
+
+ // Shows the popover on the specified position. Renders it if not already
+ show: function() {
+ if (this.isHidden) {
+ if (!this.el) {
+ this.render();
+ }
+ this.el.show();
+ this.position();
+ this.isHidden = false;
+ this.trigger('show');
+ }
+ },
+
+
+ // Hides the popover, through CSS, but does not remove it from the DOM
+ hide: function() {
+ if (!this.isHidden) {
+ this.el.hide();
+ this.isHidden = true;
+ this.trigger('hide');
+ }
+ },
+
+
+ // Creates `this.el` and renders content inside of it
+ render: function() {
+ var _this = this;
+ var options = this.options;
+
+ this.el = $('<div class="fc-popover"/>')
+ .addClass(options.className || '')
+ .css({
+ // position initially to the top left to avoid creating scrollbars
+ top: 0,
+ left: 0
+ })
+ .append(options.content)
+ .appendTo(options.parentEl);
+
+ // when a click happens on anything inside with a 'fc-close' className, hide the popover
+ this.el.on('click', '.fc-close', function() {
+ _this.hide();
+ });
+
+ if (options.autoHide) {
+ this.listenTo($(document), 'mousedown', this.documentMousedown);
+ }
+ },
+
+
+ // Triggered when the user clicks *anywhere* in the document, for the autoHide feature
+ documentMousedown: function(ev) {
+ // only hide the popover if the click happened outside the popover
+ if (this.el && !$(ev.target).closest(this.el).length) {
+ this.hide();
+ }
+ },
+
+
+ // Hides and unregisters any handlers
+ removeElement: function() {
+ this.hide();
+
+ if (this.el) {
+ this.el.remove();
+ this.el = null;
+ }
+
+ this.stopListeningTo($(document), 'mousedown');
+ },
+
+
+ // Positions the popover optimally, using the top/left/right options
+ position: function() {
+ var options = this.options;
+ var origin = this.el.offsetParent().offset();
+ var width = this.el.outerWidth();
+ var height = this.el.outerHeight();
+ var windowEl = $(window);
+ var viewportEl = getScrollParent(this.el);
+ var viewportTop;
+ var viewportLeft;
+ var viewportOffset;
+ var top; // the "position" (not "offset") values for the popover
+ var left; //
+
+ // compute top and left
+ top = options.top || 0;
+ if (options.left !== undefined) {
+ left = options.left;
+ }
+ else if (options.right !== undefined) {
+ left = options.right - width; // derive the left value from the right value
+ }
+ else {
+ left = 0;
+ }
+
+ if (viewportEl.is(window) || viewportEl.is(document)) { // normalize getScrollParent's result
+ viewportEl = windowEl;
+ viewportTop = 0; // the window is always at the top left
+ viewportLeft = 0; // (and .offset() won't work if called here)
+ }
+ else {
+ viewportOffset = viewportEl.offset();
+ viewportTop = viewportOffset.top;
+ viewportLeft = viewportOffset.left;
+ }
+
+ // if the window is scrolled, it causes the visible area to be further down
+ viewportTop += windowEl.scrollTop();
+ viewportLeft += windowEl.scrollLeft();
+
+ // constrain to the view port. if constrained by two edges, give precedence to top/left
+ if (options.viewportConstrain !== false) {
+ top = Math.min(top, viewportTop + viewportEl.outerHeight() - height - this.margin);
+ top = Math.max(top, viewportTop + this.margin);
+ left = Math.min(left, viewportLeft + viewportEl.outerWidth() - width - this.margin);
+ left = Math.max(left, viewportLeft + this.margin);
+ }
+
+ this.el.css({
+ top: top - origin.top,
+ left: left - origin.left
+ });
+ },
+
+
+ // Triggers a callback. Calls a function in the option hash of the same name.
+ // Arguments beyond the first `name` are forwarded on.
+ // TODO: better code reuse for this. Repeat code
+ trigger: function(name) {
+ if (this.options[name]) {
+ this.options[name].apply(this, Array.prototype.slice.call(arguments, 1));
+ }
+ }
+
+});
+
+;;
+
+/*
+A cache for the left/right/top/bottom/width/height values for one or more elements.
+Works with both offset (from topleft document) and position (from offsetParent).
+
+options:
+- els
+- isHorizontal
+- isVertical
+*/
+var CoordCache = FC.CoordCache = Class.extend({
+
+ els: null, // jQuery set (assumed to be siblings)
+ forcedOffsetParentEl: null, // options can override the natural offsetParent
+ origin: null, // {left,top} position of offsetParent of els
+ boundingRect: null, // constrain cordinates to this rectangle. {left,right,top,bottom} or null
+ isHorizontal: false, // whether to query for left/right/width
+ isVertical: false, // whether to query for top/bottom/height
+
+ // arrays of coordinates (offsets from topleft of document)
+ lefts: null,
+ rights: null,
+ tops: null,
+ bottoms: null,
+
+
+ constructor: function(options) {
+ this.els = $(options.els);
+ this.isHorizontal = options.isHorizontal;
+ this.isVertical = options.isVertical;
+ this.forcedOffsetParentEl = options.offsetParent ? $(options.offsetParent) : null;
+ },
+
+
+ // Queries the els for coordinates and stores them.
+ // Call this method before using and of the get* methods below.
+ build: function() {
+ var offsetParentEl = this.forcedOffsetParentEl || this.els.eq(0).offsetParent();
+
+ this.origin = offsetParentEl.offset();
+ this.boundingRect = this.queryBoundingRect();
+
+ if (this.isHorizontal) {
+ this.buildElHorizontals();
+ }
+ if (this.isVertical) {
+ this.buildElVerticals();
+ }
+ },
+
+
+ // Destroys all internal data about coordinates, freeing memory
+ clear: function() {
+ this.origin = null;
+ this.boundingRect = null;
+ this.lefts = null;
+ this.rights = null;
+ this.tops = null;
+ this.bottoms = null;
+ },
+
+
+ // When called, if coord caches aren't built, builds them
+ ensureBuilt: function() {
+ if (!this.origin) {
+ this.build();
+ }
+ },
+
+
+ // Compute and return what the elements' bounding rectangle is, from the user's perspective.
+ // Right now, only returns a rectangle if constrained by an overflow:scroll element.
+ queryBoundingRect: function() {
+ var scrollParentEl = getScrollParent(this.els.eq(0));
+
+ if (!scrollParentEl.is(document)) {
+ return getClientRect(scrollParentEl);
+ }
+ },
+
+
+ // Populates the left/right internal coordinate arrays
+ buildElHorizontals: function() {
+ var lefts = [];
+ var rights = [];
+
+ this.els.each(function(i, node) {
+ var el = $(node);
+ var left = el.offset().left;
+ var width = el.outerWidth();
+
+ lefts.push(left);
+ rights.push(left + width);
+ });
+
+ this.lefts = lefts;
+ this.rights = rights;
+ },
+
+
+ // Populates the top/bottom internal coordinate arrays
+ buildElVerticals: function() {
+ var tops = [];
+ var bottoms = [];
+
+ this.els.each(function(i, node) {
+ var el = $(node);
+ var top = el.offset().top;
+ var height = el.outerHeight();
+
+ tops.push(top);
+ bottoms.push(top + height);
+ });
+
+ this.tops = tops;
+ this.bottoms = bottoms;
+ },
+
+
+ // Given a left offset (from document left), returns the index of the el that it horizontally intersects.
+ // If no intersection is made, or outside of the boundingRect, returns undefined.
+ getHorizontalIndex: function(leftOffset) {
+ this.ensureBuilt();
+
+ var boundingRect = this.boundingRect;
+ var lefts = this.lefts;
+ var rights = this.rights;
+ var len = lefts.length;
+ var i;
+
+ if (!boundingRect || (leftOffset >= boundingRect.left && leftOffset < boundingRect.right)) {
+ for (i = 0; i < len; i++) {
+ if (leftOffset >= lefts[i] && leftOffset < rights[i]) {
+ return i;
+ }
+ }
+ }
+ },
+
+
+ // Given a top offset (from document top), returns the index of the el that it vertically intersects.
+ // If no intersection is made, or outside of the boundingRect, returns undefined.
+ getVerticalIndex: function(topOffset) {
+ this.ensureBuilt();
+
+ var boundingRect = this.boundingRect;
+ var tops = this.tops;
+ var bottoms = this.bottoms;
+ var len = tops.length;
+ var i;
+
+ if (!boundingRect || (topOffset >= boundingRect.top && topOffset < boundingRect.bottom)) {
+ for (i = 0; i < len; i++) {
+ if (topOffset >= tops[i] && topOffset < bottoms[i]) {
+ return i;
+ }
+ }
+ }
+ },
+
+
+ // Gets the left offset (from document left) of the element at the given index
+ getLeftOffset: function(leftIndex) {
+ this.ensureBuilt();
+ return this.lefts[leftIndex];
+ },
+
+
+ // Gets the left position (from offsetParent left) of the element at the given index
+ getLeftPosition: function(leftIndex) {
+ this.ensureBuilt();
+ return this.lefts[leftIndex] - this.origin.left;
+ },
+
+
+ // Gets the right offset (from document left) of the element at the given index.
+ // This value is NOT relative to the document's right edge, like the CSS concept of "right" would be.
+ getRightOffset: function(leftIndex) {
+ this.ensureBuilt();
+ return this.rights[leftIndex];
+ },
+
+
+ // Gets the right position (from offsetParent left) of the element at the given index.
+ // This value is NOT relative to the offsetParent's right edge, like the CSS concept of "right" would be.
+ getRightPosition: function(leftIndex) {
+ this.ensureBuilt();
+ return this.rights[leftIndex] - this.origin.left;
+ },
+
+
+ // Gets the width of the element at the given index
+ getWidth: function(leftIndex) {
+ this.ensureBuilt();
+ return this.rights[leftIndex] - this.lefts[leftIndex];
+ },
+
+
+ // Gets the top offset (from document top) of the element at the given index
+ getTopOffset: function(topIndex) {
+ this.ensureBuilt();
+ return this.tops[topIndex];
+ },
+
+
+ // Gets the top position (from offsetParent top) of the element at the given position
+ getTopPosition: function(topIndex) {
+ this.ensureBuilt();
+ return this.tops[topIndex] - this.origin.top;
+ },
+
+ // Gets the bottom offset (from the document top) of the element at the given index.
+ // This value is NOT relative to the offsetParent's bottom edge, like the CSS concept of "bottom" would be.
+ getBottomOffset: function(topIndex) {
+ this.ensureBuilt();
+ return this.bottoms[topIndex];
+ },
+
+
+ // Gets the bottom position (from the offsetParent top) of the element at the given index.
+ // This value is NOT relative to the offsetParent's bottom edge, like the CSS concept of "bottom" would be.
+ getBottomPosition: function(topIndex) {
+ this.ensureBuilt();
+ return this.bottoms[topIndex] - this.origin.top;
+ },
+
+
+ // Gets the height of the element at the given index
+ getHeight: function(topIndex) {
+ this.ensureBuilt();
+ return this.bottoms[topIndex] - this.tops[topIndex];
+ }
+
+});
+
+;;
+
+/* Tracks a drag's mouse movement, firing various handlers
+----------------------------------------------------------------------------------------------------------------------*/
+// TODO: use Emitter
+
+var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMixin, {
+
+ options: null,
+
+ // for IE8 bug-fighting behavior
+ subjectEl: null,
+ subjectHref: null,
+
+ // coordinates of the initial mousedown
+ originX: null,
+ originY: null,
+
+ // the wrapping element that scrolls, or MIGHT scroll if there's overflow.
+ // TODO: do this for wrappers that have overflow:hidden as well.
+ scrollEl: null,
+
+ isInteracting: false,
+ isDistanceSurpassed: false,
+ isDelayEnded: false,
+ isDragging: false,
+ isTouch: false,
+
+ delay: null,
+ delayTimeoutId: null,
+ minDistance: null,
+
+ handleTouchScrollProxy: null, // calls handleTouchScroll, always bound to `this`
+
+
+ constructor: function(options) {
+ this.options = options || {};
+ this.handleTouchScrollProxy = proxy(this, 'handleTouchScroll');
+ this.initMouseIgnoring(500);
+ },
+
+
+ // Interaction (high-level)
+ // -----------------------------------------------------------------------------------------------------------------
+
+
+ startInteraction: function(ev, extraOptions) {
+ var isTouch = getEvIsTouch(ev);
+
+ if (ev.type === 'mousedown') {
+ if (this.isIgnoringMouse) {
+ return;
+ }
+ else if (!isPrimaryMouseButton(ev)) {
+ return;
+ }
+ else {
+ ev.preventDefault(); // prevents native selection in most browsers
+ }
+ }
+
+ if (!this.isInteracting) {
+
+ // process options
+ extraOptions = extraOptions || {};
+ this.delay = firstDefined(extraOptions.delay, this.options.delay, 0);
+ this.minDistance = firstDefined(extraOptions.distance, this.options.distance, 0);
+ this.subjectEl = this.options.subjectEl;
+
+ this.isInteracting = true;
+ this.isTouch = isTouch;
+ this.isDelayEnded = false;
+ this.isDistanceSurpassed = false;
+
+ this.originX = getEvX(ev);
+ this.originY = getEvY(ev);
+ this.scrollEl = getScrollParent($(ev.target));
+
+ this.bindHandlers();
+ this.initAutoScroll();
+ this.handleInteractionStart(ev);
+ this.startDelay(ev);
+
+ if (!this.minDistance) {
+ this.handleDistanceSurpassed(ev);
+ }
+ }
+ },
+
+
+ handleInteractionStart: function(ev) {
+ this.trigger('interactionStart', ev);
+ },
+
+
+ endInteraction: function(ev, isCancelled) {
+ if (this.isInteracting) {
+ this.endDrag(ev);
+
+ if (this.delayTimeoutId) {
+ clearTimeout(this.delayTimeoutId);
+ this.delayTimeoutId = null;
+ }
+
+ this.destroyAutoScroll();
+ this.unbindHandlers();
+
+ this.isInteracting = false;
+ this.handleInteractionEnd(ev, isCancelled);
+
+ // a touchstart+touchend on the same element will result in the following addition simulated events:
+ // mouseover + mouseout + click
+ // let's ignore these bogus events
+ if (this.isTouch) {
+ this.tempIgnoreMouse();
+ }
+ }
+ },
+
+
+ handleInteractionEnd: function(ev, isCancelled) {
+ this.trigger('interactionEnd', ev, isCancelled || false);
+ },
+
+
+ // Binding To DOM
+ // -----------------------------------------------------------------------------------------------------------------
+
+
+ bindHandlers: function() {
+ var _this = this;
+ var touchStartIgnores = 1;
+
+ if (this.isTouch) {
+ this.listenTo($(document), {
+ touchmove: this.handleTouchMove,
+ touchend: this.endInteraction,
+ touchcancel: this.endInteraction,
+
+ // Sometimes touchend doesn't fire
+ // (can't figure out why. touchcancel doesn't fire either. has to do with scrolling?)
+ // If another touchstart happens, we know it's bogus, so cancel the drag.
+ // touchend will continue to be broken until user does a shorttap/scroll, but this is best we can do.
+ touchstart: function(ev) {
+ if (touchStartIgnores) { // bindHandlers is called from within a touchstart,
+ touchStartIgnores--; // and we don't want this to fire immediately, so ignore.
+ }
+ else {
+ _this.endInteraction(ev, true); // isCancelled=true
+ }
+ }
+ });
+
+ // listen to ALL scroll actions on the page
+ if (
+ !bindAnyScroll(this.handleTouchScrollProxy) && // hopefully this works and short-circuits the rest
+ this.scrollEl // otherwise, attach a single handler to this
+ ) {
+ this.listenTo(this.scrollEl, 'scroll', this.handleTouchScroll);
+ }
+ }
+ else {
+ this.listenTo($(document), {
+ mousemove: this.handleMouseMove,
+ mouseup: this.endInteraction
+ });
+ }
+
+ this.listenTo($(document), {
+ selectstart: preventDefault, // don't allow selection while dragging
+ contextmenu: preventDefault // long taps would open menu on Chrome dev tools
+ });
+ },
+
+
+ unbindHandlers: function() {
+ this.stopListeningTo($(document));
+
+ // unbind scroll listening
+ unbindAnyScroll(this.handleTouchScrollProxy);
+ if (this.scrollEl) {
+ this.stopListeningTo(this.scrollEl, 'scroll');
+ }
+ },
+
+
+ // Drag (high-level)
+ // -----------------------------------------------------------------------------------------------------------------
+
+
+ // extraOptions ignored if drag already started
+ startDrag: function(ev, extraOptions) {
+ this.startInteraction(ev, extraOptions); // ensure interaction began
+
+ if (!this.isDragging) {
+ this.isDragging = true;
+ this.handleDragStart(ev);
+ }
+ },
+
+
+ handleDragStart: function(ev) {
+ this.trigger('dragStart', ev);
+ this.initHrefHack();
+ },
+
+
+ handleMove: function(ev) {
+ var dx = getEvX(ev) - this.originX;
+ var dy = getEvY(ev) - this.originY;
+ var minDistance = this.minDistance;
+ var distanceSq; // current distance from the origin, squared
+
+ if (!this.isDistanceSurpassed) {
+ distanceSq = dx * dx + dy * dy;
+ if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
+ this.handleDistanceSurpassed(ev);
+ }
+ }
+
+ if (this.isDragging) {
+ this.handleDrag(dx, dy, ev);
+ }
+ },
+
+
+ // Called while the mouse is being moved and when we know a legitimate drag is taking place
+ handleDrag: function(dx, dy, ev) {
+ this.trigger('drag', dx, dy, ev);
+ this.updateAutoScroll(ev); // will possibly cause scrolling
+ },
+
+
+ endDrag: function(ev) {
+ if (this.isDragging) {
+ this.isDragging = false;
+ this.handleDragEnd(ev);
+ }
+ },
+
+
+ handleDragEnd: function(ev) {
+ this.trigger('dragEnd', ev);
+ this.destroyHrefHack();
+ },
+
+
+ // Delay
+ // -----------------------------------------------------------------------------------------------------------------
+
+
+ startDelay: function(initialEv) {
+ var _this = this;
+
+ if (this.delay) {
+ this.delayTimeoutId = setTimeout(function() {
+ _this.handleDelayEnd(initialEv);
+ }, this.delay);
+ }
+ else {
+ this.handleDelayEnd(initialEv);
+ }
+ },
+
+
+ handleDelayEnd: function(initialEv) {
+ this.isDelayEnded = true;
+
+ if (this.isDistanceSurpassed) {
+ this.startDrag(initialEv);
+ }
+ },
+
+
+ // Distance
+ // -----------------------------------------------------------------------------------------------------------------
+
+
+ handleDistanceSurpassed: function(ev) {
+ this.isDistanceSurpassed = true;
+
+ if (this.isDelayEnded) {
+ this.startDrag(ev);
+ }
+ },
+
+
+ // Mouse / Touch
+ // -----------------------------------------------------------------------------------------------------------------
+
+
+ handleTouchMove: function(ev) {
+ // prevent inertia and touchmove-scrolling while dragging
+ if (this.isDragging) {
+ ev.preventDefault();
+ }
+
+ this.handleMove(ev);
+ },
+
+
+ handleMouseMove: function(ev) {
+ this.handleMove(ev);
+ },
+
+
+ // Scrolling (unrelated to auto-scroll)
+ // -----------------------------------------------------------------------------------------------------------------
+
+
+ handleTouchScroll: function(ev) {
+ // if the drag is being initiated by touch, but a scroll happens before
+ // the drag-initiating delay is over, cancel the drag
+ if (!this.isDragging) {
+ this.endInteraction(ev, true); // isCancelled=true
+ }
+ },
+
+
+ // <A> HREF Hack
+ // -----------------------------------------------------------------------------------------------------------------
+
+
+ initHrefHack: function() {
+ var subjectEl = this.subjectEl;
+
+ // remove a mousedown'd <a>'s href so it is not visited (IE8 bug)
+ if ((this.subjectHref = subjectEl ? subjectEl.attr('href') : null)) {
+ subjectEl.removeAttr('href');
+ }
+ },
+
+
+ destroyHrefHack: function() {
+ var subjectEl = this.subjectEl;
+ var subjectHref = this.subjectHref;
+
+ // restore a mousedown'd <a>'s href (for IE8 bug)
+ setTimeout(function() { // must be outside of the click's execution
+ if (subjectHref) {
+ subjectEl.attr('href', subjectHref);
+ }
+ }, 0);
+ },
+
+
+ // Utils
+ // -----------------------------------------------------------------------------------------------------------------
+
+
+ // Triggers a callback. Calls a function in the option hash of the same name.
+ // Arguments beyond the first `name` are forwarded on.
+ trigger: function(name) {
+ if (this.options[name]) {
+ this.options[name].apply(this, Array.prototype.slice.call(arguments, 1));
+ }
+ // makes _methods callable by event name. TODO: kill this
+ if (this['_' + name]) {
+ this['_' + name].apply(this, Array.prototype.slice.call(arguments, 1));
+ }
+ }
+
+
+});
+
+;;
+/*
+this.scrollEl is set in DragListener
+*/
+DragListener.mixin({
+
+ isAutoScroll: false,
+
+ scrollBounds: null, // { top, bottom, left, right }
+ scrollTopVel: null, // pixels per second
+ scrollLeftVel: null, // pixels per second
+ scrollIntervalId: null, // ID of setTimeout for scrolling animation loop
+
+ // defaults
+ scrollSensitivity: 30, // pixels from edge for scrolling to start
+ scrollSpeed: 200, // pixels per second, at maximum speed
+ scrollIntervalMs: 50, // millisecond wait between scroll increment
+
+
+ initAutoScroll: function() {
+ var scrollEl = this.scrollEl;
+
+ this.isAutoScroll =
+ this.options.scroll &&
+ scrollEl &&
+ !scrollEl.is(window) &&
+ !scrollEl.is(document);
+
+ if (this.isAutoScroll) {
+ // debounce makes sure rapid calls don't happen
+ this.listenTo(scrollEl, 'scroll', debounce(this.handleDebouncedScroll, 100));
+ }
+ },
+
+
+ destroyAutoScroll: function() {
+ this.endAutoScroll(); // kill any animation loop
+
+ // remove the scroll handler if there is a scrollEl
+ if (this.isAutoScroll) {
+ this.stopListeningTo(this.scrollEl, 'scroll'); // will probably get removed by unbindHandlers too :(
+ }
+ },
+
+
+ // Computes and stores the bounding rectangle of scrollEl
+ computeScrollBounds: function() {
+ if (this.isAutoScroll) {
+ this.scrollBounds = getOuterRect(this.scrollEl);
+ // TODO: use getClientRect in future. but prevents auto scrolling when on top of scrollbars
+ }
+ },
+
+
+ // Called when the dragging is in progress and scrolling should be updated
+ updateAutoScroll: function(ev) {
+ var sensitivity = this.scrollSensitivity;
+ var bounds = this.scrollBounds;
+ var topCloseness, bottomCloseness;
+ var leftCloseness, rightCloseness;
+ var topVel = 0;
+ var leftVel = 0;
+
+ if (bounds) { // only scroll if scrollEl exists
+
+ // compute closeness to edges. valid range is from 0.0 - 1.0
+ topCloseness = (sensitivity - (getEvY(ev) - bounds.top)) / sensitivity;
+ bottomCloseness = (sensitivity - (bounds.bottom - getEvY(ev))) / sensitivity;
+ leftCloseness = (sensitivity - (getEvX(ev) - bounds.left)) / sensitivity;
+ rightCloseness = (sensitivity - (bounds.right - getEvX(ev))) / sensitivity;
+
+ // translate vertical closeness into velocity.
+ // mouse must be completely in bounds for velocity to happen.
+ if (topCloseness >= 0 && topCloseness <= 1) {
+ topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up
+ }
+ else if (bottomCloseness >= 0 && bottomCloseness <= 1) {
+ topVel = bottomCloseness * this.scrollSpeed;
+ }
+
+ // translate horizontal closeness into velocity
+ if (leftCloseness >= 0 && leftCloseness <= 1) {
+ leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left
+ }
+ else if (rightCloseness >= 0 && rightCloseness <= 1) {
+ leftVel = rightCloseness * this.scrollSpeed;
+ }
+ }
+
+ this.setScrollVel(topVel, leftVel);
+ },
+
+
+ // Sets the speed-of-scrolling for the scrollEl
+ setScrollVel: function(topVel, leftVel) {
+
+ this.scrollTopVel = topVel;
+ this.scrollLeftVel = leftVel;
+
+ this.constrainScrollVel(); // massages into realistic values
+
+ // if there is non-zero velocity, and an animation loop hasn't already started, then START
+ if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) {
+ this.scrollIntervalId = setInterval(
+ proxy(this, 'scrollIntervalFunc'), // scope to `this`
+ this.scrollIntervalMs
+ );
+ }
+ },
+
+
+ // Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way
+ constrainScrollVel: function() {
+ var el = this.scrollEl;
+
+ if (this.scrollTopVel < 0) { // scrolling up?
+ if (el.scrollTop() <= 0) { // already scrolled all the way up?
+ this.scrollTopVel = 0;
+ }
+ }
+ else if (this.scrollTopVel > 0) { // scrolling down?
+ if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down?
+ this.scrollTopVel = 0;
+ }
+ }
+
+ if (this.scrollLeftVel < 0) { // scrolling left?
+ if (el.scrollLeft() <= 0) { // already scrolled all the left?
+ this.scrollLeftVel = 0;
+ }
+ }
+ else if (this.scrollLeftVel > 0) { // scrolling right?
+ if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right?
+ this.scrollLeftVel = 0;
+ }
+ }
+ },
+
+
+ // This function gets called during every iteration of the scrolling animation loop
+ scrollIntervalFunc: function() {
+ var el = this.scrollEl;
+ var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by
+
+ // change the value of scrollEl's scroll
+ if (this.scrollTopVel) {
+ el.scrollTop(el.scrollTop() + this.scrollTopVel * frac);
+ }
+ if (this.scrollLeftVel) {
+ el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac);
+ }
+
+ this.constrainScrollVel(); // since the scroll values changed, recompute the velocities
+
+ // if scrolled all the way, which causes the vels to be zero, stop the animation loop
+ if (!this.scrollTopVel && !this.scrollLeftVel) {
+ this.endAutoScroll();
+ }
+ },
+
+
+ // Kills any existing scrolling animation loop
+ endAutoScroll: function() {
+ if (this.scrollIntervalId) {
+ clearInterval(this.scrollIntervalId);
+ this.scrollIntervalId = null;
+
+ this.handleScrollEnd();
+ }
+ },
+
+
+ // Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce)
+ handleDebouncedScroll: function() {
+ // recompute all coordinates, but *only* if this is *not* part of our scrolling animation
+ if (!this.scrollIntervalId) {
+ this.handleScrollEnd();
+ }
+ },
+
+
+ // Called when scrolling has stopped, whether through auto scroll, or the user scrolling
+ handleScrollEnd: function() {
+ }
+
+});
+;;
+
+/* Tracks mouse movements over a component and raises events about which hit the mouse is over.
+------------------------------------------------------------------------------------------------------------------------
+options:
+- subjectEl
+- subjectCenter
+*/
+
+var HitDragListener = DragListener.extend({
+
+ component: null, // converts coordinates to hits
+ // methods: prepareHits, releaseHits, queryHit
+
+ origHit: null, // the hit the mouse was over when listening started
+ hit: null, // the hit the mouse is over
+ coordAdjust: null, // delta that will be added to the mouse coordinates when computing collisions
+
+
+ constructor: function(component, options) {
+ DragListener.call(this, options); // call the super-constructor
+
+ this.component = component;
+ },
+
+
+ // Called when drag listening starts (but a real drag has not necessarily began).
+ // ev might be undefined if dragging was started manually.
+ handleInteractionStart: function(ev) {
+ var subjectEl = this.subjectEl;
+ var subjectRect;
+ var origPoint;
+ var point;
+
+ this.computeCoords();
+
+ if (ev) {
+ origPoint = { left: getEvX(ev), top: getEvY(ev) };
+ point = origPoint;
+
+ // constrain the point to bounds of the element being dragged
+ if (subjectEl) {
+ subjectRect = getOuterRect(subjectEl); // used for centering as well
+ point = constrainPoint(point, subjectRect);
+ }
+
+ this.origHit = this.queryHit(point.left, point.top);
+
+ // treat the center of the subject as the collision point?
+ if (subjectEl && this.options.subjectCenter) {
+
+ // only consider the area the subject overlaps the hit. best for large subjects.
+ // TODO: skip this if hit didn't supply left/right/top/bottom
+ if (this.origHit) {
+ subjectRect = intersectRects(this.origHit, subjectRect) ||
+ subjectRect; // in case there is no intersection
+ }
+
+ point = getRectCenter(subjectRect);
+ }
+
+ this.coordAdjust = diffPoints(point, origPoint); // point - origPoint
+ }
+ else {
+ this.origHit = null;
+ this.coordAdjust = null;
+ }
+
+ // call the super-method. do it after origHit has been computed
+ DragListener.prototype.handleInteractionStart.apply(this, arguments);
+ },
+
+
+ // Recomputes the drag-critical positions of elements
+ computeCoords: function() {
+ this.component.prepareHits();
+ this.computeScrollBounds(); // why is this here??????
+ },
+
+
+ // Called when the actual drag has started
+ handleDragStart: function(ev) {
+ var hit;
+
+ DragListener.prototype.handleDragStart.apply(this, arguments); // call the super-method
+
+ // might be different from this.origHit if the min-distance is large
+ hit = this.queryHit(getEvX(ev), getEvY(ev));
+
+ // report the initial hit the mouse is over
+ // especially important if no min-distance and drag starts immediately
+ if (hit) {
+ this.handleHitOver(hit);
+ }
+ },
+
+
+ // Called when the drag moves
+ handleDrag: function(dx, dy, ev) {
+ var hit;
+
+ DragListener.prototype.handleDrag.apply(this, arguments); // call the super-method
+
+ hit = this.queryHit(getEvX(ev), getEvY(ev));
+
+ if (!isHitsEqual(hit, this.hit)) { // a different hit than before?
+ if (this.hit) {
+ this.handleHitOut();
+ }
+ if (hit) {
+ this.handleHitOver(hit);
+ }
+ }
+ },
+
+
+ // Called when dragging has been stopped
+ handleDragEnd: function() {
+ this.handleHitDone();
+ DragListener.prototype.handleDragEnd.apply(this, arguments); // call the super-method
+ },
+
+
+ // Called when a the mouse has just moved over a new hit
+ handleHitOver: function(hit) {
+ var isOrig = isHitsEqual(hit, this.origHit);
+
+ this.hit = hit;
+
+ this.trigger('hitOver', this.hit, isOrig, this.origHit);
+ },
+
+
+ // Called when the mouse has just moved out of a hit
+ handleHitOut: function() {
+ if (this.hit) {
+ this.trigger('hitOut', this.hit);
+ this.handleHitDone();
+ this.hit = null;
+ }
+ },
+
+
+ // Called after a hitOut. Also called before a dragStop
+ handleHitDone: function() {
+ if (this.hit) {
+ this.trigger('hitDone', this.hit);
+ }
+ },
+
+
+ // Called when the interaction ends, whether there was a real drag or not
+ handleInteractionEnd: function() {
+ DragListener.prototype.handleInteractionEnd.apply(this, arguments); // call the super-method
+
+ this.origHit = null;
+ this.hit = null;
+
+ this.component.releaseHits();
+ },
+
+
+ // Called when scrolling has stopped, whether through auto scroll, or the user scrolling
+ handleScrollEnd: function() {
+ DragListener.prototype.handleScrollEnd.apply(this, arguments); // call the super-method
+
+ this.computeCoords(); // hits' absolute positions will be in new places. recompute
+ },
+
+
+ // Gets the hit underneath the coordinates for the given mouse event
+ queryHit: function(left, top) {
+
+ if (this.coordAdjust) {
+ left += this.coordAdjust.left;
+ top += this.coordAdjust.top;
+ }
+
+ return this.component.queryHit(left, top);
+ }
+
+});
+
+
+// Returns `true` if the hits are identically equal. `false` otherwise. Must be from the same component.
+// Two null values will be considered equal, as two "out of the component" states are the same.
+function isHitsEqual(hit0, hit1) {
+
+ if (!hit0 && !hit1) {
+ return true;
+ }
+
+ if (hit0 && hit1) {
+ return hit0.component === hit1.component &&
+ isHitPropsWithin(hit0, hit1) &&
+ isHitPropsWithin(hit1, hit0); // ensures all props are identical
+ }
+
+ return false;
+}
+
+
+// Returns true if all of subHit's non-standard properties are within superHit
+function isHitPropsWithin(subHit, superHit) {
+ for (var propName in subHit) {
+ if (!/^(component|left|right|top|bottom)$/.test(propName)) {
+ if (subHit[propName] !== superHit[propName]) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+;;
+
+/* Creates a clone of an element and lets it track the mouse as it moves
+----------------------------------------------------------------------------------------------------------------------*/
+
+var MouseFollower = Class.extend(ListenerMixin, {
+
+ options: null,
+
+ sourceEl: null, // the element that will be cloned and made to look like it is dragging
+ el: null, // the clone of `sourceEl` that will track the mouse
+ parentEl: null, // the element that `el` (the clone) will be attached to
+
+ // the initial position of el, relative to the offset parent. made to match the initial offset of sourceEl
+ top0: null,
+ left0: null,
+
+ // the absolute coordinates of the initiating touch/mouse action
+ y0: null,
+ x0: null,
+
+ // the number of pixels the mouse has moved from its initial position
+ topDelta: null,
+ leftDelta: null,
+
+ isFollowing: false,
+ isHidden: false,
+ isAnimating: false, // doing the revert animation?
+
+ constructor: function(sourceEl, options) {
+ this.options = options = options || {};
+ this.sourceEl = sourceEl;
+ this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent
+ },
+
+
+ // Causes the element to start following the mouse
+ start: function(ev) {
+ if (!this.isFollowing) {
+ this.isFollowing = true;
+
+ this.y0 = getEvY(ev);
+ this.x0 = getEvX(ev);
+ this.topDelta = 0;
+ this.leftDelta = 0;
+
+ if (!this.isHidden) {
+ this.updatePosition();
+ }
+
+ if (getEvIsTouch(ev)) {
+ this.listenTo($(document), 'touchmove', this.handleMove);
+ }
+ else {
+ this.listenTo($(document), 'mousemove', this.handleMove);
+ }
+ }
+ },
+
+
+ // Causes the element to stop following the mouse. If shouldRevert is true, will animate back to original position.
+ // `callback` gets invoked when the animation is complete. If no animation, it is invoked immediately.
+ stop: function(shouldRevert, callback) {
+ var _this = this;
+ var revertDuration = this.options.revertDuration;
+
+ function complete() {
+ this.isAnimating = false;
+ _this.removeElement();
+
+ this.top0 = this.left0 = null; // reset state for future updatePosition calls
+
+ if (callback) {
+ callback();
+ }
+ }
+
+ if (this.isFollowing && !this.isAnimating) { // disallow more than one stop animation at a time
+ this.isFollowing = false;
+
+ this.stopListeningTo($(document));
+
+ if (shouldRevert && revertDuration && !this.isHidden) { // do a revert animation?
+ this.isAnimating = true;
+ this.el.animate({
+ top: this.top0,
+ left: this.left0
+ }, {
+ duration: revertDuration,
+ complete: complete
+ });
+ }
+ else {
+ complete();
+ }
+ }
+ },
+
+
+ // Gets the tracking element. Create it if necessary
+ getEl: function() {
+ var el = this.el;
+
+ if (!el) {
+ this.sourceEl.width(); // hack to force IE8 to compute correct bounding box
+ el = this.el = this.sourceEl.clone()
+ .addClass(this.options.additionalClass || '')
+ .css({
+ position: 'absolute',
+ visibility: '', // in case original element was hidden (commonly through hideEvents())
+ display: this.isHidden ? 'none' : '', // for when initially hidden
+ margin: 0,
+ right: 'auto', // erase and set width instead
+ bottom: 'auto', // erase and set height instead
+ width: this.sourceEl.width(), // explicit height in case there was a 'right' value
+ height: this.sourceEl.height(), // explicit width in case there was a 'bottom' value
+ opacity: this.options.opacity || '',
+ zIndex: this.options.zIndex
+ });
+
+ // we don't want long taps or any mouse interaction causing selection/menus.
+ // would use preventSelection(), but that prevents selectstart, causing problems.
+ el.addClass('fc-unselectable');
+
+ el.appendTo(this.parentEl);
+ }
+
+ return el;
+ },
+
+
+ // Removes the tracking element if it has already been created
+ removeElement: function() {
+ if (this.el) {
+ this.el.remove();
+ this.el = null;
+ }
+ },
+
+
+ // Update the CSS position of the tracking element
+ updatePosition: function() {
+ var sourceOffset;
+ var origin;
+
+ this.getEl(); // ensure this.el
+
+ // make sure origin info was computed
+ if (this.top0 === null) {
+ this.sourceEl.width(); // hack to force IE8 to compute correct bounding box
+ sourceOffset = this.sourceEl.offset();
+ origin = this.el.offsetParent().offset();
+ this.top0 = sourceOffset.top - origin.top;
+ this.left0 = sourceOffset.left - origin.left;
+ }
+
+ this.el.css({
+ top: this.top0 + this.topDelta,
+ left: this.left0 + this.leftDelta
+ });
+ },
+
+
+ // Gets called when the user moves the mouse
+ handleMove: function(ev) {
+ this.topDelta = getEvY(ev) - this.y0;
+ this.leftDelta = getEvX(ev) - this.x0;
+
+ if (!this.isHidden) {
+ this.updatePosition();
+ }
+ },
+
+
+ // Temporarily makes the tracking element invisible. Can be called before following starts
+ hide: function() {
+ if (!this.isHidden) {
+ this.isHidden = true;
+ if (this.el) {
+ this.el.hide();
+ }
+ }
+ },
+
+
+ // Show the tracking element after it has been temporarily hidden
+ show: function() {
+ if (this.isHidden) {
+ this.isHidden = false;
+ this.updatePosition();
+ this.getEl().show();
+ }
+ }
+
+});
+
+;;
+
+/* An abstract class comprised of a "grid" of areas that each represent a specific datetime
+----------------------------------------------------------------------------------------------------------------------*/
+
+var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
+
+ view: null, // a View object
+ isRTL: null, // shortcut to the view's isRTL option
+
+ start: null,
+ end: null,
+
+ el: null, // the containing element
+ elsByFill: null, // a hash of jQuery element sets used for rendering each fill. Keyed by fill name.
+
+ // derived from options
+ eventTimeFormat: null,
+ displayEventTime: null,
+ displayEventEnd: null,
+
+ minResizeDuration: null, // TODO: hack. set by subclasses. minumum event resize duration
+
+ // if defined, holds the unit identified (ex: "year" or "month") that determines the level of granularity
+ // of the date areas. if not defined, assumes to be day and time granularity.
+ // TODO: port isTimeScale into same system?
+ largeUnit: null,
+
+ dayDragListener: null,
+ segDragListener: null,
+ segResizeListener: null,
+ externalDragListener: null,
+
+
+ constructor: function(view) {
+ this.view = view;
+ this.isRTL = view.opt('isRTL');
+ this.elsByFill = {};
+
+ this.dayDragListener = this.buildDayDragListener();
+ this.initMouseIgnoring();
+ },
+
+
+ /* Options
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Generates the format string used for event time text, if not explicitly defined by 'timeFormat'
+ computeEventTimeFormat: function() {
+ return this.view.opt('smallTimeFormat');
+ },
+
+
+ // Determines whether events should have their end times displayed, if not explicitly defined by 'displayEventTime'.
+ // Only applies to non-all-day events.
+ computeDisplayEventTime: function() {
+ return true;
+ },
+
+
+ // Determines whether events should have their end times displayed, if not explicitly defined by 'displayEventEnd'
+ computeDisplayEventEnd: function() {
+ return true;
+ },
+
+
+ /* Dates
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Tells the grid about what period of time to display.
+ // Any date-related internal data should be generated.
+ setRange: function(range) {
+ this.start = range.start.clone();
+ this.end = range.end.clone();
+
+ this.rangeUpdated();
+ this.processRangeOptions();
+ },
+
+
+ // Called when internal variables that rely on the range should be updated
+ rangeUpdated: function() {
+ },
+
+
+ // Updates values that rely on options and also relate to range
+ processRangeOptions: function() {
+ var view = this.view;
+ var displayEventTime;
+ var displayEventEnd;
+
+ this.eventTimeFormat =
+ view.opt('eventTimeFormat') ||
+ view.opt('timeFormat') || // deprecated
+ this.computeEventTimeFormat();
+
+ displayEventTime = view.opt('displayEventTime');
+ if (displayEventTime == null) {
+ displayEventTime = this.computeDisplayEventTime(); // might be based off of range
+ }
+
+ displayEventEnd = view.opt('displayEventEnd');
+ if (displayEventEnd == null) {
+ displayEventEnd = this.computeDisplayEventEnd(); // might be based off of range
+ }
+
+ this.displayEventTime = displayEventTime;
+ this.displayEventEnd = displayEventEnd;
+ },
+
+
+ // Converts a span (has unzoned start/end and any other grid-specific location information)
+ // into an array of segments (pieces of events whose format is decided by the grid).
+ spanToSegs: function(span) {
+ // subclasses must implement
+ },
+
+
+ // Diffs the two dates, returning a duration, based on granularity of the grid
+ // TODO: port isTimeScale into this system?
+ diffDates: function(a, b) {
+ if (this.largeUnit) {
+ return diffByUnit(a, b, this.largeUnit);
+ }
+ else {
+ return diffDayTime(a, b);
+ }
+ },
+
+
+ /* Hit Area
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Called before one or more queryHit calls might happen. Should prepare any cached coordinates for queryHit
+ prepareHits: function() {
+ },
+
+
+ // Called when queryHit calls have subsided. Good place to clear any coordinate caches.
+ releaseHits: function() {
+ },
+
+
+ // Given coordinates from the topleft of the document, return data about the date-related area underneath.
+ // Can return an object with arbitrary properties (although top/right/left/bottom are encouraged).
+ // Must have a `grid` property, a reference to this current grid. TODO: avoid this
+ // The returned object will be processed by getHitSpan and getHitEl.
+ queryHit: function(leftOffset, topOffset) {
+ },
+
+
+ // Given position-level information about a date-related area within the grid,
+ // should return an object with at least a start/end date. Can provide other information as well.
+ getHitSpan: function(hit) {
+ },
+
+
+ // Given position-level information about a date-related area within the grid,
+ // should return a jQuery element that best represents it. passed to dayClick callback.
+ getHitEl: function(hit) {
+ },
+
+
+ /* Rendering
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Sets the container element that the grid should render inside of.
+ // Does other DOM-related initializations.
+ setElement: function(el) {
+ this.el = el;
+ preventSelection(el);
+
+ this.bindDayHandler('touchstart', this.dayTouchStart);
+ this.bindDayHandler('mousedown', this.dayMousedown);
+
+ // attach event-element-related handlers. in Grid.events
+ // same garbage collection note as above.
+ this.bindSegHandlers();
+
+ this.bindGlobalHandlers();
+ },
+
+
+ bindDayHandler: function(name, handler) {
+ var _this = this;
+
+ // attach a handler to the grid's root element.
+ // jQuery will take care of unregistering them when removeElement gets called.
+ this.el.on(name, function(ev) {
+ if (
+ !$(ev.target).is('.fc-event-container *, .fc-more') && // not an an event element, or "more.." link
+ !$(ev.target).closest('.fc-popover').length // not on a popover (like the "more.." events one)
+ ) {
+ return handler.call(_this, ev);
+ }
+ });
+ },
+
+
+ // Removes the grid's container element from the DOM. Undoes any other DOM-related attachments.
+ // DOES NOT remove any content beforehand (doesn't clear events or call unrenderDates), unlike View
+ removeElement: function() {
+ this.unbindGlobalHandlers();
+ this.clearDragListeners();
+
+ this.el.remove();
+
+ // NOTE: we don't null-out this.el for the same reasons we don't do it within View::removeElement
+ },
+
+
+ // Renders the basic structure of grid view before any content is rendered
+ renderSkeleton: function() {
+ // subclasses should implement
+ },
+
+
+ // Renders the grid's date-related content (like areas that represent days/times).
+ // Assumes setRange has already been called and the skeleton has already been rendered.
+ renderDates: function() {
+ // subclasses should implement
+ },
+
+
+ // Unrenders the grid's date-related content
+ unrenderDates: function() {
+ // subclasses should implement
+ },
+
+
+ /* Handlers
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Binds DOM handlers to elements that reside outside the grid, such as the document
+ bindGlobalHandlers: function() {
+ this.listenTo($(document), {
+ dragstart: this.externalDragStart, // jqui
+ sortstart: this.externalDragStart // jqui
+ });
+ },
+
+
+ // Unbinds DOM handlers from elements that reside outside the grid
+ unbindGlobalHandlers: function() {
+ this.stopListeningTo($(document));
+ },
+
+
+ // Process a mousedown on an element that represents a day. For day clicking and selecting.
+ dayMousedown: function(ev) {
+ if (!this.isIgnoringMouse) {
+ this.dayDragListener.startInteraction(ev, {
+ //distance: 5, // needs more work if we want dayClick to fire correctly
+ });
+ }
+ },
+
+
+ dayTouchStart: function(ev) {
+ var view = this.view;
+
+ // HACK to prevent a user's clickaway for unselecting a range or an event
+ // from causing a dayClick.
+ if (view.isSelected || view.selectedEvent) {
+ this.tempIgnoreMouse();
+ }
+
+ this.dayDragListener.startInteraction(ev, {
+ delay: this.view.opt('longPressDelay')
+ });
+ },
+
+
+ // Creates a listener that tracks the user's drag across day elements.
+ // For day clicking and selecting.
+ buildDayDragListener: function() {
+ var _this = this;
+ var view = this.view;
+ var isSelectable = view.opt('selectable');
+ var dayClickHit; // null if invalid dayClick
+ var selectionSpan; // null if invalid selection
+
+ // this listener tracks a mousedown on a day element, and a subsequent drag.
+ // if the drag ends on the same day, it is a 'dayClick'.
+ // if 'selectable' is enabled, this listener also detects selections.
+ var dragListener = new HitDragListener(this, {
+ scroll: view.opt('dragScroll'),
+ interactionStart: function() {
+ dayClickHit = dragListener.origHit; // for dayClick, where no dragging happens
+ },
+ dragStart: function() {
+ view.unselect(); // since we could be rendering a new selection, we want to clear any old one
+ },
+ hitOver: function(hit, isOrig, origHit) {
+ if (origHit) { // click needs to have started on a hit
+
+ // if user dragged to another cell at any point, it can no longer be a dayClick
+ if (!isOrig) {
+ dayClickHit = null;
+ }
+
+ if (isSelectable) {
+ selectionSpan = _this.computeSelection(
+ _this.getHitSpan(origHit),
+ _this.getHitSpan(hit)
+ );
+ if (selectionSpan) {
+ _this.renderSelection(selectionSpan);
+ }
+ else if (selectionSpan === false) {
+ disableCursor();
+ }
+ }
+ }
+ },
+ hitOut: function() {
+ dayClickHit = null;
+ selectionSpan = null;
+ _this.unrenderSelection();
+ enableCursor();
+ },
+ interactionEnd: function(ev, isCancelled) {
+ if (!isCancelled) {
+ if (
+ dayClickHit &&
+ !_this.isIgnoringMouse // see hack in dayTouchStart
+ ) {
+ view.triggerDayClick(
+ _this.getHitSpan(dayClickHit),
+ _this.getHitEl(dayClickHit),
+ ev
+ );
+ }
+ if (selectionSpan) {
+ // the selection will already have been rendered. just report it
+ view.reportSelection(selectionSpan, ev);
+ }
+ enableCursor();
+ }
+ }
+ });
+
+ return dragListener;
+ },
+
+
+ // Kills all in-progress dragging.
+ // Useful for when public API methods that result in re-rendering are invoked during a drag.
+ // Also useful for when touch devices misbehave and don't fire their touchend.
+ clearDragListeners: function() {
+ this.dayDragListener.endInteraction();
+
+ if (this.segDragListener) {
+ this.segDragListener.endInteraction(); // will clear this.segDragListener
+ }
+ if (this.segResizeListener) {
+ this.segResizeListener.endInteraction(); // will clear this.segResizeListener
+ }
+ if (this.externalDragListener) {
+ this.externalDragListener.endInteraction(); // will clear this.externalDragListener
+ }
+ },
+
+
+ /* Event Helper
+ ------------------------------------------------------------------------------------------------------------------*/
+ // TODO: should probably move this to Grid.events, like we did event dragging / resizing
+
+
+ // Renders a mock event at the given event location, which contains zoned start/end properties.
+ // Returns all mock event elements.
+ renderEventLocationHelper: function(eventLocation, sourceSeg) {
+ var fakeEvent = this.fabricateHelperEvent(eventLocation, sourceSeg);
+
+ return this.renderHelper(fakeEvent, sourceSeg); // do the actual rendering
+ },
+
+
+ // Builds a fake event given zoned event date properties and a segment is should be inspired from.
+ // The range's end can be null, in which case the mock event that is rendered will have a null end time.
+ // `sourceSeg` is the internal segment object involved in the drag. If null, something external is dragging.
+ fabricateHelperEvent: function(eventLocation, sourceSeg) {
+ var fakeEvent = sourceSeg ? createObject(sourceSeg.event) : {}; // mask the original event object if possible
+
+ fakeEvent.start = eventLocation.start.clone();
+ fakeEvent.end = eventLocation.end ? eventLocation.end.clone() : null;
+ fakeEvent.allDay = null; // force it to be freshly computed by normalizeEventDates
+ this.view.calendar.normalizeEventDates(fakeEvent);
+
+ // this extra className will be useful for differentiating real events from mock events in CSS
+ fakeEvent.className = (fakeEvent.className || []).concat('fc-helper');
+
+ // if something external is being dragged in, don't render a resizer
+ if (!sourceSeg) {
+ fakeEvent.editable = false;
+ }
+
+ return fakeEvent;
+ },
+
+
+ // Renders a mock event. Given zoned event date properties.
+ // Must return all mock event elements.
+ renderHelper: function(eventLocation, sourceSeg) {
+ // subclasses must implement
+ },
+
+
+ // Unrenders a mock event
+ unrenderHelper: function() {
+ // subclasses must implement
+ },
+
+
+ /* Selection
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of a selection. Will highlight by default but can be overridden by subclasses.
+ // Given a span (unzoned start/end and other misc data)
+ renderSelection: function(span) {
+ this.renderHighlight(span);
+ },
+
+
+ // Unrenders any visual indications of a selection. Will unrender a highlight by default.
+ unrenderSelection: function() {
+ this.unrenderHighlight();
+ },
+
+
+ // Given the first and last date-spans of a selection, returns another date-span object.
+ // Subclasses can override and provide additional data in the span object. Will be passed to renderSelection().
+ // Will return false if the selection is invalid and this should be indicated to the user.
+ // Will return null/undefined if a selection invalid but no error should be reported.
+ computeSelection: function(span0, span1) {
+ var span = this.computeSelectionSpan(span0, span1);
+
+ if (span && !this.view.calendar.isSelectionSpanAllowed(span)) {
+ return false;
+ }
+
+ return span;
+ },
+
+
+ // Given two spans, must return the combination of the two.
+ // TODO: do this separation of concerns (combining VS validation) for event dnd/resize too.
+ computeSelectionSpan: function(span0, span1) {
+ var dates = [ span0.start, span0.end, span1.start, span1.end ];
+
+ dates.sort(compareNumbers); // sorts chronologically. works with Moments
+
+ return { start: dates[0].clone(), end: dates[3].clone() };
+ },
+
+
+ /* Highlight
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders an emphasis on the given date range. Given a span (unzoned start/end and other misc data)
+ renderHighlight: function(span) {
+ this.renderFill('highlight', this.spanToSegs(span));
+ },
+
+
+ // Unrenders the emphasis on a date range
+ unrenderHighlight: function() {
+ this.unrenderFill('highlight');
+ },
+
+
+ // Generates an array of classNames for rendering the highlight. Used by the fill system.
+ highlightSegClasses: function() {
+ return [ 'fc-highlight' ];
+ },
+
+
+ /* Business Hours
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderBusinessHours: function() {
+ },
+
+
+ unrenderBusinessHours: function() {
+ },
+
+
+ /* Now Indicator
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ getNowIndicatorUnit: function() {
+ },
+
+
+ renderNowIndicator: function(date) {
+ },
+
+
+ unrenderNowIndicator: function() {
+ },
+
+
+ /* Fill System (highlight, background events, business hours)
+ --------------------------------------------------------------------------------------------------------------------
+ TODO: remove this system. like we did in TimeGrid
+ */
+
+
+ // Renders a set of rectangles over the given segments of time.
+ // MUST RETURN a subset of segs, the segs that were actually rendered.
+ // Responsible for populating this.elsByFill. TODO: better API for expressing this requirement
+ renderFill: function(type, segs) {
+ // subclasses must implement
+ },
+
+
+ // Unrenders a specific type of fill that is currently rendered on the grid
+ unrenderFill: function(type) {
+ var el = this.elsByFill[type];
+
+ if (el) {
+ el.remove();
+ delete this.elsByFill[type];
+ }
+ },
+
+
+ // Renders and assigns an `el` property for each fill segment. Generic enough to work with different types.
+ // Only returns segments that successfully rendered.
+ // To be harnessed by renderFill (implemented by subclasses).
+ // Analagous to renderFgSegEls.
+ renderFillSegEls: function(type, segs) {
+ var _this = this;
+ var segElMethod = this[type + 'SegEl'];
+ var html = '';
+ var renderedSegs = [];
+ var i;
+
+ if (segs.length) {
+
+ // build a large concatenation of segment HTML
+ for (i = 0; i < segs.length; i++) {
+ html += this.fillSegHtml(type, segs[i]);
+ }
+
+ // Grab individual elements from the combined HTML string. Use each as the default rendering.
+ // Then, compute the 'el' for each segment.
+ $(html).each(function(i, node) {
+ var seg = segs[i];
+ var el = $(node);
+
+ // allow custom filter methods per-type
+ if (segElMethod) {
+ el = segElMethod.call(_this, seg, el);
+ }
+
+ if (el) { // custom filters did not cancel the render
+ el = $(el); // allow custom filter to return raw DOM node
+
+ // correct element type? (would be bad if a non-TD were inserted into a table for example)
+ if (el.is(_this.fillSegTag)) {
+ seg.el = el;
+ renderedSegs.push(seg);
+ }
+ }
+ });
+ }
+
+ return renderedSegs;
+ },
+
+
+ fillSegTag: 'div', // subclasses can override
+
+
+ // Builds the HTML needed for one fill segment. Generic enought o work with different types.
+ fillSegHtml: function(type, seg) {
+
+ // custom hooks per-type
+ var classesMethod = this[type + 'SegClasses'];
+ var cssMethod = this[type + 'SegCss'];
+
+ var classes = classesMethod ? classesMethod.call(this, seg) : [];
+ var css = cssToStr(cssMethod ? cssMethod.call(this, seg) : {});
+
+ return '<' + this.fillSegTag +
+ (classes.length ? ' class="' + classes.join(' ') + '"' : '') +
+ (css ? ' style="' + css + '"' : '') +
+ ' />';
+ },
+
+
+
+ /* Generic rendering utilities for subclasses
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Computes HTML classNames for a single-day element
+ getDayClasses: function(date) {
+ var view = this.view;
+ var today = view.calendar.getNow();
+ var classes = [ 'fc-' + dayIDs[date.day()] ];
+
+ if (
+ view.intervalDuration.as('months') == 1 &&
+ date.month() != view.intervalStart.month()
+ ) {
+ classes.push('fc-other-month');
+ }
+
+ if (date.isSame(today, 'day')) {
+ classes.push(
+ 'fc-today',
+ view.highlightStateClass
+ );
+ }
+ else if (date < today) {
+ classes.push('fc-past');
+ }
+ else {
+ classes.push('fc-future');
+ }
+
+ return classes;
+ }
+
+});
+
+;;
+
+/* Event-rendering and event-interaction methods for the abstract Grid class
+----------------------------------------------------------------------------------------------------------------------*/
+
+Grid.mixin({
+
+ mousedOverSeg: null, // the segment object the user's mouse is over. null if over nothing
+ isDraggingSeg: false, // is a segment being dragged? boolean
+ isResizingSeg: false, // is a segment being resized? boolean
+ isDraggingExternal: false, // jqui-dragging an external element? boolean
+ segs: null, // the *event* segments currently rendered in the grid. TODO: rename to `eventSegs`
+
+
+ // Renders the given events onto the grid
+ renderEvents: function(events) {
+ var bgEvents = [];
+ var fgEvents = [];
+ var i;
+
+ for (i = 0; i < events.length; i++) {
+ (isBgEvent(events[i]) ? bgEvents : fgEvents).push(events[i]);
+ }
+
+ this.segs = [].concat( // record all segs
+ this.renderBgEvents(bgEvents),
+ this.renderFgEvents(fgEvents)
+ );
+ },
+
+
+ renderBgEvents: function(events) {
+ var segs = this.eventsToSegs(events);
+
+ // renderBgSegs might return a subset of segs, segs that were actually rendered
+ return this.renderBgSegs(segs) || segs;
+ },
+
+
+ renderFgEvents: function(events) {
+ var segs = this.eventsToSegs(events);
+
+ // renderFgSegs might return a subset of segs, segs that were actually rendered
+ return this.renderFgSegs(segs) || segs;
+ },
+
+
+ // Unrenders all events currently rendered on the grid
+ unrenderEvents: function() {
+ this.handleSegMouseout(); // trigger an eventMouseout if user's mouse is over an event
+ this.clearDragListeners();
+
+ this.unrenderFgSegs();
+ this.unrenderBgSegs();
+
+ this.segs = null;
+ },
+
+
+ // Retrieves all rendered segment objects currently rendered on the grid
+ getEventSegs: function() {
+ return this.segs || [];
+ },
+
+
+ /* Foreground Segment Rendering
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders foreground event segments onto the grid. May return a subset of segs that were rendered.
+ renderFgSegs: function(segs) {
+ // subclasses must implement
+ },
+
+
+ // Unrenders all currently rendered foreground segments
+ unrenderFgSegs: function() {
+ // subclasses must implement
+ },
+
+
+ // Renders and assigns an `el` property for each foreground event segment.
+ // Only returns segments that successfully rendered.
+ // A utility that subclasses may use.
+ renderFgSegEls: function(segs, disableResizing) {
+ var view = this.view;
+ var html = '';
+ var renderedSegs = [];
+ var i;
+
+ if (segs.length) { // don't build an empty html string
+
+ // build a large concatenation of event segment HTML
+ for (i = 0; i < segs.length; i++) {
+ html += this.fgSegHtml(segs[i], disableResizing);
+ }
+
+ // Grab individual elements from the combined HTML string. Use each as the default rendering.
+ // Then, compute the 'el' for each segment. An el might be null if the eventRender callback returned false.
+ $(html).each(function(i, node) {
+ var seg = segs[i];
+ var el = view.resolveEventEl(seg.event, $(node));
+
+ if (el) {
+ el.data('fc-seg', seg); // used by handlers
+ seg.el = el;
+ renderedSegs.push(seg);
+ }
+ });
+ }
+
+ return renderedSegs;
+ },
+
+
+ // Generates the HTML for the default rendering of a foreground event segment. Used by renderFgSegEls()
+ fgSegHtml: function(seg, disableResizing) {
+ // subclasses should implement
+ },
+
+
+ /* Background Segment Rendering
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders the given background event segments onto the grid.
+ // Returns a subset of the segs that were actually rendered.
+ renderBgSegs: function(segs) {
+ return this.renderFill('bgEvent', segs);
+ },
+
+
+ // Unrenders all the currently rendered background event segments
+ unrenderBgSegs: function() {
+ this.unrenderFill('bgEvent');
+ },
+
+
+ // Renders a background event element, given the default rendering. Called by the fill system.
+ bgEventSegEl: function(seg, el) {
+ return this.view.resolveEventEl(seg.event, el); // will filter through eventRender
+ },
+
+
+ // Generates an array of classNames to be used for the default rendering of a background event.
+ // Called by the fill system.
+ bgEventSegClasses: function(seg) {
+ var event = seg.event;
+ var source = event.source || {};
+
+ return [ 'fc-bgevent' ].concat(
+ event.className,
+ source.className || []
+ );
+ },
+
+
+ // Generates a semicolon-separated CSS string to be used for the default rendering of a background event.
+ // Called by the fill system.
+ bgEventSegCss: function(seg) {
+ return {
+ 'background-color': this.getSegSkinCss(seg)['background-color']
+ };
+ },
+
+
+ // Generates an array of classNames to be used for the rendering business hours overlay. Called by the fill system.
+ businessHoursSegClasses: function(seg) {
+ return [ 'fc-nonbusiness', 'fc-bgevent' ];
+ },
+
+
+ /* Handlers
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Attaches event-element-related handlers to the container element and leverage bubbling
+ bindSegHandlers: function() {
+ this.bindSegHandler('touchstart', this.handleSegTouchStart);
+ this.bindSegHandler('touchend', this.handleSegTouchEnd);
+ this.bindSegHandler('mouseenter', this.handleSegMouseover);
+ this.bindSegHandler('mouseleave', this.handleSegMouseout);
+ this.bindSegHandler('mousedown', this.handleSegMousedown);
+ this.bindSegHandler('click', this.handleSegClick);
+ },
+
+
+ // Executes a handler for any a user-interaction on a segment.
+ // Handler gets called with (seg, ev), and with the `this` context of the Grid
+ bindSegHandler: function(name, handler) {
+ var _this = this;
+
+ this.el.on(name, '.fc-event-container > *', function(ev) {
+ var seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEvents
+
+ // only call the handlers if there is not a drag/resize in progress
+ if (seg && !_this.isDraggingSeg && !_this.isResizingSeg) {
+ return handler.call(_this, seg, ev); // context will be the Grid
+ }
+ });
+ },
+
+
+ handleSegClick: function(seg, ev) {
+ return this.view.trigger('eventClick', seg.el[0], seg.event, ev); // can return `false` to cancel
+ },
+
+
+ // Updates internal state and triggers handlers for when an event element is moused over
+ handleSegMouseover: function(seg, ev) {
+ if (
+ !this.isIgnoringMouse &&
+ !this.mousedOverSeg
+ ) {
+ this.mousedOverSeg = seg;
+ seg.el.addClass('fc-allow-mouse-resize');
+ this.view.trigger('eventMouseover', seg.el[0], seg.event, ev);
+ }
+ },
+
+
+ // Updates internal state and triggers handlers for when an event element is moused out.
+ // Can be given no arguments, in which case it will mouseout the segment that was previously moused over.
+ handleSegMouseout: function(seg, ev) {
+ ev = ev || {}; // if given no args, make a mock mouse event
+
+ if (this.mousedOverSeg) {
+ seg = seg || this.mousedOverSeg; // if given no args, use the currently moused-over segment
+ this.mousedOverSeg = null;
+ seg.el.removeClass('fc-allow-mouse-resize');
+ this.view.trigger('eventMouseout', seg.el[0], seg.event, ev);
+ }
+ },
+
+
+ handleSegMousedown: function(seg, ev) {
+ var isResizing = this.startSegResize(seg, ev, { distance: 5 });
+
+ if (!isResizing && this.view.isEventDraggable(seg.event)) {
+ this.buildSegDragListener(seg)
+ .startInteraction(ev, {
+ distance: 5
+ });
+ }
+ },
+
+
+ handleSegTouchStart: function(seg, ev) {
+ var view = this.view;
+ var event = seg.event;
+ var isSelected = view.isEventSelected(event);
+ var isDraggable = view.isEventDraggable(event);
+ var isResizable = view.isEventResizable(event);
+ var isResizing = false;
+ var dragListener;
+
+ if (isSelected && isResizable) {
+ // only allow resizing of the event is selected
+ isResizing = this.startSegResize(seg, ev);
+ }
+
+ if (!isResizing && (isDraggable || isResizable)) { // allowed to be selected?
+
+ dragListener = isDraggable ?
+ this.buildSegDragListener(seg) :
+ this.buildSegSelectListener(seg); // seg isn't draggable, but still needs to be selected
+
+ dragListener.startInteraction(ev, { // won't start if already started
+ delay: isSelected ? 0 : this.view.opt('longPressDelay') // do delay if not already selected
+ });
+ }
+
+ // a long tap simulates a mouseover. ignore this bogus mouseover.
+ this.tempIgnoreMouse();
+ },
+
+
+ handleSegTouchEnd: function(seg, ev) {
+ // touchstart+touchend = click, which simulates a mouseover.
+ // ignore this bogus mouseover.
+ this.tempIgnoreMouse();
+ },
+
+
+ // returns boolean whether resizing actually started or not.
+ // assumes the seg allows resizing.
+ // `dragOptions` are optional.
+ startSegResize: function(seg, ev, dragOptions) {
+ if ($(ev.target).is('.fc-resizer')) {
+ this.buildSegResizeListener(seg, $(ev.target).is('.fc-start-resizer'))
+ .startInteraction(ev, dragOptions);
+ return true;
+ }
+ return false;
+ },
+
+
+
+ /* Event Dragging
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Builds a listener that will track user-dragging on an event segment.
+ // Generic enough to work with any type of Grid.
+ // Has side effect of setting/unsetting `segDragListener`
+ buildSegDragListener: function(seg) {
+ var _this = this;
+ var view = this.view;
+ var calendar = view.calendar;
+ var el = seg.el;
+ var event = seg.event;
+ var isDragging;
+ var mouseFollower; // A clone of the original element that will move with the mouse
+ var dropLocation; // zoned event date properties
+
+ if (this.segDragListener) {
+ return this.segDragListener;
+ }
+
+ // Tracks mouse movement over the *view's* coordinate map. Allows dragging and dropping between subcomponents
+ // of the view.
+ var dragListener = this.segDragListener = new HitDragListener(view, {
+ scroll: view.opt('dragScroll'),
+ subjectEl: el,
+ subjectCenter: true,
+ interactionStart: function(ev) {
+ isDragging = false;
+ mouseFollower = new MouseFollower(seg.el, {
+ additionalClass: 'fc-dragging',
+ parentEl: view.el,
+ opacity: dragListener.isTouch ? null : view.opt('dragOpacity'),
+ revertDuration: view.opt('dragRevertDuration'),
+ zIndex: 2 // one above the .fc-view
+ });
+ mouseFollower.hide(); // don't show until we know this is a real drag
+ mouseFollower.start(ev);
+ },
+ dragStart: function(ev) {
+ if (dragListener.isTouch && !view.isEventSelected(event)) {
+ // if not previously selected, will fire after a delay. then, select the event
+ view.selectEvent(event);
+ }
+ isDragging = true;
+ _this.handleSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported
+ _this.segDragStart(seg, ev);
+ view.hideEvent(event); // hide all event segments. our mouseFollower will take over
+ },
+ hitOver: function(hit, isOrig, origHit) {
+ var dragHelperEls;
+
+ // starting hit could be forced (DayGrid.limit)
+ if (seg.hit) {
+ origHit = seg.hit;
+ }
+
+ // since we are querying the parent view, might not belong to this grid
+ dropLocation = _this.computeEventDrop(
+ origHit.component.getHitSpan(origHit),
+ hit.component.getHitSpan(hit),
+ event
+ );
+
+ if (dropLocation && !calendar.isEventSpanAllowed(_this.eventToSpan(dropLocation), event)) {
+ disableCursor();
+ dropLocation = null;
+ }
+
+ // if a valid drop location, have the subclass render a visual indication
+ if (dropLocation && (dragHelperEls = view.renderDrag(dropLocation, seg))) {
+
+ dragHelperEls.addClass('fc-dragging');
+ if (!dragListener.isTouch) {
+ _this.applyDragOpacity(dragHelperEls);
+ }
+
+ mouseFollower.hide(); // if the subclass is already using a mock event "helper", hide our own
+ }
+ else {
+ mouseFollower.show(); // otherwise, have the helper follow the mouse (no snapping)
+ }
+
+ if (isOrig) {
+ dropLocation = null; // needs to have moved hits to be a valid drop
+ }
+ },
+ hitOut: function() { // called before mouse moves to a different hit OR moved out of all hits
+ view.unrenderDrag(); // unrender whatever was done in renderDrag
+ mouseFollower.show(); // show in case we are moving out of all hits
+ dropLocation = null;
+ },
+ hitDone: function() { // Called after a hitOut OR before a dragEnd
+ enableCursor();
+ },
+ interactionEnd: function(ev) {
+ // do revert animation if hasn't changed. calls a callback when finished (whether animation or not)
+ mouseFollower.stop(!dropLocation, function() {
+ if (isDragging) {
+ view.unrenderDrag();
+ view.showEvent(event);
+ _this.segDragStop(seg, ev);
+ }
+ if (dropLocation) {
+ view.reportEventDrop(event, dropLocation, this.largeUnit, el, ev);
+ }
+ });
+ _this.segDragListener = null;
+ }
+ });
+
+ return dragListener;
+ },
+
+
+ // seg isn't draggable, but let's use a generic DragListener
+ // simply for the delay, so it can be selected.
+ // Has side effect of setting/unsetting `segDragListener`
+ buildSegSelectListener: function(seg) {
+ var _this = this;
+ var view = this.view;
+ var event = seg.event;
+
+ if (this.segDragListener) {
+ return this.segDragListener;
+ }
+
+ var dragListener = this.segDragListener = new DragListener({
+ dragStart: function(ev) {
+ if (dragListener.isTouch && !view.isEventSelected(event)) {
+ // if not previously selected, will fire after a delay. then, select the event
+ view.selectEvent(event);
+ }
+ },
+ interactionEnd: function(ev) {
+ _this.segDragListener = null;
+ }
+ });
+
+ return dragListener;
+ },
+
+
+ // Called before event segment dragging starts
+ segDragStart: function(seg, ev) {
+ this.isDraggingSeg = true;
+ this.view.trigger('eventDragStart', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy
+ },
+
+
+ // Called after event segment dragging stops
+ segDragStop: function(seg, ev) {
+ this.isDraggingSeg = false;
+ this.view.trigger('eventDragStop', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy
+ },
+
+
+ // Given the spans an event drag began, and the span event was dropped, calculates the new zoned start/end/allDay
+ // values for the event. Subclasses may override and set additional properties to be used by renderDrag.
+ // A falsy returned value indicates an invalid drop.
+ // DOES NOT consider overlap/constraint.
+ computeEventDrop: function(startSpan, endSpan, event) {
+ var calendar = this.view.calendar;
+ var dragStart = startSpan.start;
+ var dragEnd = endSpan.start;
+ var delta;
+ var dropLocation; // zoned event date properties
+
+ if (dragStart.hasTime() === dragEnd.hasTime()) {
+ delta = this.diffDates(dragEnd, dragStart);
+
+ // if an all-day event was in a timed area and it was dragged to a different time,
+ // guarantee an end and adjust start/end to have times
+ if (event.allDay && durationHasTime(delta)) {
+ dropLocation = {
+ start: event.start.clone(),
+ end: calendar.getEventEnd(event), // will be an ambig day
+ allDay: false // for normalizeEventTimes
+ };
+ calendar.normalizeEventTimes(dropLocation);
+ }
+ // othewise, work off existing values
+ else {
+ dropLocation = {
+ start: event.start.clone(),
+ end: event.end ? event.end.clone() : null,
+ allDay: event.allDay // keep it the same
+ };
+ }
+
+ dropLocation.start.add(delta);
+ if (dropLocation.end) {
+ dropLocation.end.add(delta);
+ }
+ }
+ else {
+ // if switching from day <-> timed, start should be reset to the dropped date, and the end cleared
+ dropLocation = {
+ start: dragEnd.clone(),
+ end: null, // end should be cleared
+ allDay: !dragEnd.hasTime()
+ };
+ }
+
+ return dropLocation;
+ },
+
+
+ // Utility for apply dragOpacity to a jQuery set
+ applyDragOpacity: function(els) {
+ var opacity = this.view.opt('dragOpacity');
+
+ if (opacity != null) {
+ els.each(function(i, node) {
+ // Don't use jQuery (will set an IE filter), do it the old fashioned way.
+ // In IE8, a helper element will disappears if there's a filter.
+ node.style.opacity = opacity;
+ });
+ }
+ },
+
+
+ /* External Element Dragging
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Called when a jQuery UI drag is initiated anywhere in the DOM
+ externalDragStart: function(ev, ui) {
+ var view = this.view;
+ var el;
+ var accept;
+
+ if (view.opt('droppable')) { // only listen if this setting is on
+ el = $((ui ? ui.item : null) || ev.target);
+
+ // Test that the dragged element passes the dropAccept selector or filter function.
+ // FYI, the default is "*" (matches all)
+ accept = view.opt('dropAccept');
+ if ($.isFunction(accept) ? accept.call(el[0], el) : el.is(accept)) {
+ if (!this.isDraggingExternal) { // prevent double-listening if fired twice
+ this.listenToExternalDrag(el, ev, ui);
+ }
+ }
+ }
+ },
+
+
+ // Called when a jQuery UI drag starts and it needs to be monitored for dropping
+ listenToExternalDrag: function(el, ev, ui) {
+ var _this = this;
+ var calendar = this.view.calendar;
+ var meta = getDraggedElMeta(el); // extra data about event drop, including possible event to create
+ var dropLocation; // a null value signals an unsuccessful drag
+
+ // listener that tracks mouse movement over date-associated pixel regions
+ var dragListener = _this.externalDragListener = new HitDragListener(this, {
+ interactionStart: function() {
+ _this.isDraggingExternal = true;
+ },
+ hitOver: function(hit) {
+ dropLocation = _this.computeExternalDrop(
+ hit.component.getHitSpan(hit), // since we are querying the parent view, might not belong to this grid
+ meta
+ );
+
+ if ( // invalid hit?
+ dropLocation &&
+ !calendar.isExternalSpanAllowed(_this.eventToSpan(dropLocation), dropLocation, meta.eventProps)
+ ) {
+ disableCursor();
+ dropLocation = null;
+ }
+
+ if (dropLocation) {
+ _this.renderDrag(dropLocation); // called without a seg parameter
+ }
+ },
+ hitOut: function() {
+ dropLocation = null; // signal unsuccessful
+ },
+ hitDone: function() { // Called after a hitOut OR before a dragEnd
+ enableCursor();
+ _this.unrenderDrag();
+ },
+ interactionEnd: function(ev) {
+ if (dropLocation) { // element was dropped on a valid hit
+ _this.view.reportExternalDrop(meta, dropLocation, el, ev, ui);
+ }
+ _this.isDraggingExternal = false;
+ _this.externalDragListener = null;
+ }
+ });
+
+ dragListener.startDrag(ev); // start listening immediately
+ },
+
+
+ // Given a hit to be dropped upon, and misc data associated with the jqui drag (guaranteed to be a plain object),
+ // returns the zoned start/end dates for the event that would result from the hypothetical drop. end might be null.
+ // Returning a null value signals an invalid drop hit.
+ // DOES NOT consider overlap/constraint.
+ computeExternalDrop: function(span, meta) {
+ var calendar = this.view.calendar;
+ var dropLocation = {
+ start: calendar.applyTimezone(span.start), // simulate a zoned event start date
+ end: null
+ };
+
+ // if dropped on an all-day span, and element's metadata specified a time, set it
+ if (meta.startTime && !dropLocation.start.hasTime()) {
+ dropLocation.start.time(meta.startTime);
+ }
+
+ if (meta.duration) {
+ dropLocation.end = dropLocation.start.clone().add(meta.duration);
+ }
+
+ return dropLocation;
+ },
+
+
+
+ /* Drag Rendering (for both events and an external elements)
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of an event or external element being dragged.
+ // `dropLocation` contains hypothetical start/end/allDay values the event would have if dropped. end can be null.
+ // `seg` is the internal segment object that is being dragged. If dragging an external element, `seg` is null.
+ // A truthy returned value indicates this method has rendered a helper element.
+ // Must return elements used for any mock events.
+ renderDrag: function(dropLocation, seg) {
+ // subclasses must implement
+ },
+
+
+ // Unrenders a visual indication of an event or external element being dragged
+ unrenderDrag: function() {
+ // subclasses must implement
+ },
+
+
+ /* Resizing
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Creates a listener that tracks the user as they resize an event segment.
+ // Generic enough to work with any type of Grid.
+ buildSegResizeListener: function(seg, isStart) {
+ var _this = this;
+ var view = this.view;
+ var calendar = view.calendar;
+ var el = seg.el;
+ var event = seg.event;
+ var eventEnd = calendar.getEventEnd(event);
+ var isDragging;
+ var resizeLocation; // zoned event date properties. falsy if invalid resize
+
+ // Tracks mouse movement over the *grid's* coordinate map
+ var dragListener = this.segResizeListener = new HitDragListener(this, {
+ scroll: view.opt('dragScroll'),
+ subjectEl: el,
+ interactionStart: function() {
+ isDragging = false;
+ },
+ dragStart: function(ev) {
+ isDragging = true;
+ _this.handleSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported
+ _this.segResizeStart(seg, ev);
+ },
+ hitOver: function(hit, isOrig, origHit) {
+ var origHitSpan = _this.getHitSpan(origHit);
+ var hitSpan = _this.getHitSpan(hit);
+
+ resizeLocation = isStart ?
+ _this.computeEventStartResize(origHitSpan, hitSpan, event) :
+ _this.computeEventEndResize(origHitSpan, hitSpan, event);
+
+ if (resizeLocation) {
+ if (!calendar.isEventSpanAllowed(_this.eventToSpan(resizeLocation), event)) {
+ disableCursor();
+ resizeLocation = null;
+ }
+ // no change? (TODO: how does this work with timezones?)
+ else if (resizeLocation.start.isSame(event.start) && resizeLocation.end.isSame(eventEnd)) {
+ resizeLocation = null;
+ }
+ }
+
+ if (resizeLocation) {
+ view.hideEvent(event);
+ _this.renderEventResize(resizeLocation, seg);
+ }
+ },
+ hitOut: function() { // called before mouse moves to a different hit OR moved out of all hits
+ resizeLocation = null;
+ },
+ hitDone: function() { // resets the rendering to show the original event
+ _this.unrenderEventResize();
+ view.showEvent(event);
+ enableCursor();
+ },
+ interactionEnd: function(ev) {
+ if (isDragging) {
+ _this.segResizeStop(seg, ev);
+ }
+ if (resizeLocation) { // valid date to resize to?
+ view.reportEventResize(event, resizeLocation, this.largeUnit, el, ev);
+ }
+ _this.segResizeListener = null;
+ }
+ });
+
+ return dragListener;
+ },
+
+
+ // Called before event segment resizing starts
+ segResizeStart: function(seg, ev) {
+ this.isResizingSeg = true;
+ this.view.trigger('eventResizeStart', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy
+ },
+
+
+ // Called after event segment resizing stops
+ segResizeStop: function(seg, ev) {
+ this.isResizingSeg = false;
+ this.view.trigger('eventResizeStop', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy
+ },
+
+
+ // Returns new date-information for an event segment being resized from its start
+ computeEventStartResize: function(startSpan, endSpan, event) {
+ return this.computeEventResize('start', startSpan, endSpan, event);
+ },
+
+
+ // Returns new date-information for an event segment being resized from its end
+ computeEventEndResize: function(startSpan, endSpan, event) {
+ return this.computeEventResize('end', startSpan, endSpan, event);
+ },
+
+
+ // Returns new zoned date information for an event segment being resized from its start OR end
+ // `type` is either 'start' or 'end'.
+ // DOES NOT consider overlap/constraint.
+ computeEventResize: function(type, startSpan, endSpan, event) {
+ var calendar = this.view.calendar;
+ var delta = this.diffDates(endSpan[type], startSpan[type]);
+ var resizeLocation; // zoned event date properties
+ var defaultDuration;
+
+ // build original values to work from, guaranteeing a start and end
+ resizeLocation = {
+ start: event.start.clone(),
+ end: calendar.getEventEnd(event),
+ allDay: event.allDay
+ };
+
+ // if an all-day event was in a timed area and was resized to a time, adjust start/end to have times
+ if (resizeLocation.allDay && durationHasTime(delta)) {
+ resizeLocation.allDay = false;
+ calendar.normalizeEventTimes(resizeLocation);
+ }
+
+ resizeLocation[type].add(delta); // apply delta to start or end
+
+ // if the event was compressed too small, find a new reasonable duration for it
+ if (!resizeLocation.start.isBefore(resizeLocation.end)) {
+
+ defaultDuration =
+ this.minResizeDuration || // TODO: hack
+ (event.allDay ?
+ calendar.defaultAllDayEventDuration :
+ calendar.defaultTimedEventDuration);
+
+ if (type == 'start') { // resizing the start?
+ resizeLocation.start = resizeLocation.end.clone().subtract(defaultDuration);
+ }
+ else { // resizing the end?
+ resizeLocation.end = resizeLocation.start.clone().add(defaultDuration);
+ }
+ }
+
+ return resizeLocation;
+ },
+
+
+ // Renders a visual indication of an event being resized.
+ // `range` has the updated dates of the event. `seg` is the original segment object involved in the drag.
+ // Must return elements used for any mock events.
+ renderEventResize: function(range, seg) {
+ // subclasses must implement
+ },
+
+
+ // Unrenders a visual indication of an event being resized.
+ unrenderEventResize: function() {
+ // subclasses must implement
+ },
+
+
+ /* Rendering Utils
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Compute the text that should be displayed on an event's element.
+ // `range` can be the Event object itself, or something range-like, with at least a `start`.
+ // If event times are disabled, or the event has no time, will return a blank string.
+ // If not specified, formatStr will default to the eventTimeFormat setting,
+ // and displayEnd will default to the displayEventEnd setting.
+ getEventTimeText: function(range, formatStr, displayEnd) {
+
+ if (formatStr == null) {
+ formatStr = this.eventTimeFormat;
+ }
+
+ if (displayEnd == null) {
+ displayEnd = this.displayEventEnd;
+ }
+
+ if (this.displayEventTime && range.start.hasTime()) {
+ if (displayEnd && range.end) {
+ return this.view.formatRange(range, formatStr);
+ }
+ else {
+ return range.start.format(formatStr);
+ }
+ }
+
+ return '';
+ },
+
+
+ // Generic utility for generating the HTML classNames for an event segment's element
+ getSegClasses: function(seg, isDraggable, isResizable) {
+ var view = this.view;
+ var event = seg.event;
+ var classes = [
+ 'fc-event',
+ seg.isStart ? 'fc-start' : 'fc-not-start',
+ seg.isEnd ? 'fc-end' : 'fc-not-end'
+ ].concat(
+ event.className,
+ event.source ? event.source.className : []
+ );
+
+ if (isDraggable) {
+ classes.push('fc-draggable');
+ }
+ if (isResizable) {
+ classes.push('fc-resizable');
+ }
+
+ // event is currently selected? attach a className.
+ if (view.isEventSelected(event)) {
+ classes.push('fc-selected');
+ }
+
+ return classes;
+ },
+
+
+ // Utility for generating event skin-related CSS properties
+ getSegSkinCss: function(seg) {
+ var event = seg.event;
+ var view = this.view;
+ var source = event.source || {};
+ var eventColor = event.color;
+ var sourceColor = source.color;
+ var optionColor = view.opt('eventColor');
+
+ return {
+ 'background-color':
+ event.backgroundColor ||
+ eventColor ||
+ source.backgroundColor ||
+ sourceColor ||
+ view.opt('eventBackgroundColor') ||
+ optionColor,
+ 'border-color':
+ event.borderColor ||
+ eventColor ||
+ source.borderColor ||
+ sourceColor ||
+ view.opt('eventBorderColor') ||
+ optionColor,
+ color:
+ event.textColor ||
+ source.textColor ||
+ view.opt('eventTextColor')
+ };
+ },
+
+
+ /* Converting events -> eventRange -> eventSpan -> eventSegs
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Generates an array of segments for the given single event
+ // Can accept an event "location" as well (which only has start/end and no allDay)
+ eventToSegs: function(event) {
+ return this.eventsToSegs([ event ]);
+ },
+
+
+ eventToSpan: function(event) {
+ return this.eventToSpans(event)[0];
+ },
+
+
+ // Generates spans (always unzoned) for the given event.
+ // Does not do any inverting for inverse-background events.
+ // Can accept an event "location" as well (which only has start/end and no allDay)
+ eventToSpans: function(event) {
+ var range = this.eventToRange(event);
+ return this.eventRangeToSpans(range, event);
+ },
+
+
+
+ // Converts an array of event objects into an array of event segment objects.
+ // A custom `segSliceFunc` may be given for arbitrarily slicing up events.
+ // Doesn't guarantee an order for the resulting array.
+ eventsToSegs: function(allEvents, segSliceFunc) {
+ var _this = this;
+ var eventsById = groupEventsById(allEvents);
+ var segs = [];
+
+ $.each(eventsById, function(id, events) {
+ var ranges = [];
+ var i;
+
+ for (i = 0; i < events.length; i++) {
+ ranges.push(_this.eventToRange(events[i]));
+ }
+
+ // inverse-background events (utilize only the first event in calculations)
+ if (isInverseBgEvent(events[0])) {
+ ranges = _this.invertRanges(ranges);
+
+ for (i = 0; i < ranges.length; i++) {
+ segs.push.apply(segs, // append to
+ _this.eventRangeToSegs(ranges[i], events[0], segSliceFunc));
+ }
+ }
+ // normal event ranges
+ else {
+ for (i = 0; i < ranges.length; i++) {
+ segs.push.apply(segs, // append to
+ _this.eventRangeToSegs(ranges[i], events[i], segSliceFunc));
+ }
+ }
+ });
+
+ return segs;
+ },
+
+
+ // Generates the unzoned start/end dates an event appears to occupy
+ // Can accept an event "location" as well (which only has start/end and no allDay)
+ eventToRange: function(event) {
+ return {
+ start: event.start.clone().stripZone(),
+ end: (
+ event.end ?
+ event.end.clone() :
+ // derive the end from the start and allDay. compute allDay if necessary
+ this.view.calendar.getDefaultEventEnd(
+ event.allDay != null ?
+ event.allDay :
+ !event.start.hasTime(),
+ event.start
+ )
+ ).stripZone()
+ };
+ },
+
+
+ // Given an event's range (unzoned start/end), and the event itself,
+ // slice into segments (using the segSliceFunc function if specified)
+ eventRangeToSegs: function(range, event, segSliceFunc) {
+ var spans = this.eventRangeToSpans(range, event);
+ var segs = [];
+ var i;
+
+ for (i = 0; i < spans.length; i++) {
+ segs.push.apply(segs, // append to
+ this.eventSpanToSegs(spans[i], event, segSliceFunc));
+ }
+
+ return segs;
+ },
+
+
+ // Given an event's unzoned date range, return an array of "span" objects.
+ // Subclasses can override.
+ eventRangeToSpans: function(range, event) {
+ return [ $.extend({}, range) ]; // copy into a single-item array
+ },
+
+
+ // Given an event's span (unzoned start/end and other misc data), and the event itself,
+ // slices into segments and attaches event-derived properties to them.
+ eventSpanToSegs: function(span, event, segSliceFunc) {
+ var segs = segSliceFunc ? segSliceFunc(span) : this.spanToSegs(span);
+ var i, seg;
+
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ seg.event = event;
+ seg.eventStartMS = +span.start; // TODO: not the best name after making spans unzoned
+ seg.eventDurationMS = span.end - span.start;
+ }
+
+ return segs;
+ },
+
+
+ // Produces a new array of range objects that will cover all the time NOT covered by the given ranges.
+ // SIDE EFFECT: will mutate the given array and will use its date references.
+ invertRanges: function(ranges) {
+ var view = this.view;
+ var viewStart = view.start.clone(); // need a copy
+ var viewEnd = view.end.clone(); // need a copy
+ var inverseRanges = [];
+ var start = viewStart; // the end of the previous range. the start of the new range
+ var i, range;
+
+ // ranges need to be in order. required for our date-walking algorithm
+ ranges.sort(compareRanges);
+
+ for (i = 0; i < ranges.length; i++) {
+ range = ranges[i];
+
+ // add the span of time before the event (if there is any)
+ if (range.start > start) { // compare millisecond time (skip any ambig logic)
+ inverseRanges.push({
+ start: start,
+ end: range.start
+ });
+ }
+
+ start = range.end;
+ }
+
+ // add the span of time after the last event (if there is any)
+ if (start < viewEnd) { // compare millisecond time (skip any ambig logic)
+ inverseRanges.push({
+ start: start,
+ end: viewEnd
+ });
+ }
+
+ return inverseRanges;
+ },
+
+
+ sortEventSegs: function(segs) {
+ segs.sort(proxy(this, 'compareEventSegs'));
+ },
+
+
+ // A cmp function for determining which segments should take visual priority
+ compareEventSegs: function(seg1, seg2) {
+ return seg1.eventStartMS - seg2.eventStartMS || // earlier events go first
+ seg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first
+ seg2.event.allDay - seg1.event.allDay || // tie? put all-day events first (booleans cast to 0/1)
+ compareByFieldSpecs(seg1.event, seg2.event, this.view.eventOrderSpecs);
+ }
+
+});
+
+
+/* Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+
+function isBgEvent(event) { // returns true if background OR inverse-background
+ var rendering = getEventRendering(event);
+ return rendering === 'background' || rendering === 'inverse-background';
+}
+FC.isBgEvent = isBgEvent; // export
+
+
+function isInverseBgEvent(event) {
+ return getEventRendering(event) === 'inverse-background';
+}
+
+
+function getEventRendering(event) {
+ return firstDefined((event.source || {}).rendering, event.rendering);
+}
+
+
+function groupEventsById(events) {
+ var eventsById = {};
+ var i, event;
+
+ for (i = 0; i < events.length; i++) {
+ event = events[i];
+ (eventsById[event._id] || (eventsById[event._id] = [])).push(event);
+ }
+
+ return eventsById;
+}
+
+
+// A cmp function for determining which non-inverted "ranges" (see above) happen earlier
+function compareRanges(range1, range2) {
+ return range1.start - range2.start; // earlier ranges go first
+}
+
+
+/* External-Dragging-Element Data
+----------------------------------------------------------------------------------------------------------------------*/
+
+// Require all HTML5 data-* attributes used by FullCalendar to have this prefix.
+// A value of '' will query attributes like data-event. A value of 'fc' will query attributes like data-fc-event.
+FC.dataAttrPrefix = '';
+
+// Given a jQuery element that might represent a dragged FullCalendar event, returns an intermediate data structure
+// to be used for Event Object creation.
+// A defined `.eventProps`, even when empty, indicates that an event should be created.
+function getDraggedElMeta(el) {
+ var prefix = FC.dataAttrPrefix;
+ var eventProps; // properties for creating the event, not related to date/time
+ var startTime; // a Duration
+ var duration;
+ var stick;
+
+ if (prefix) { prefix += '-'; }
+ eventProps = el.data(prefix + 'event') || null;
+
+ if (eventProps) {
+ if (typeof eventProps === 'object') {
+ eventProps = $.extend({}, eventProps); // make a copy
+ }
+ else { // something like 1 or true. still signal event creation
+ eventProps = {};
+ }
+
+ // pluck special-cased date/time properties
+ startTime = eventProps.start;
+ if (startTime == null) { startTime = eventProps.time; } // accept 'time' as well
+ duration = eventProps.duration;
+ stick = eventProps.stick;
+ delete eventProps.start;
+ delete eventProps.time;
+ delete eventProps.duration;
+ delete eventProps.stick;
+ }
+
+ // fallback to standalone attribute values for each of the date/time properties
+ if (startTime == null) { startTime = el.data(prefix + 'start'); }
+ if (startTime == null) { startTime = el.data(prefix + 'time'); } // accept 'time' as well
+ if (duration == null) { duration = el.data(prefix + 'duration'); }
+ if (stick == null) { stick = el.data(prefix + 'stick'); }
+
+ // massage into correct data types
+ startTime = startTime != null ? moment.duration(startTime) : null;
+ duration = duration != null ? moment.duration(duration) : null;
+ stick = Boolean(stick);
+
+ return { eventProps: eventProps, startTime: startTime, duration: duration, stick: stick };
+}
+
+
+;;
+
+/*
+A set of rendering and date-related methods for a visual component comprised of one or more rows of day columns.
+Prerequisite: the object being mixed into needs to be a *Grid*
+*/
+var DayTableMixin = FC.DayTableMixin = {
+
+ breakOnWeeks: false, // should create a new row for each week?
+ dayDates: null, // whole-day dates for each column. left to right
+ dayIndices: null, // for each day from start, the offset
+ daysPerRow: null,
+ rowCnt: null,
+ colCnt: null,
+ colHeadFormat: null,
+
+
+ // Populates internal variables used for date calculation and rendering
+ updateDayTable: function() {
+ var view = this.view;
+ var date = this.start.clone();
+ var dayIndex = -1;
+ var dayIndices = [];
+ var dayDates = [];
+ var daysPerRow;
+ var firstDay;
+ var rowCnt;
+
+ while (date.isBefore(this.end)) { // loop each day from start to end
+ if (view.isHiddenDay(date)) {
+ dayIndices.push(dayIndex + 0.5); // mark that it's between indices
+ }
+ else {
+ dayIndex++;
+ dayIndices.push(dayIndex);
+ dayDates.push(date.clone());
+ }
+ date.add(1, 'days');
+ }
+
+ if (this.breakOnWeeks) {
+ // count columns until the day-of-week repeats
+ firstDay = dayDates[0].day();
+ for (daysPerRow = 1; daysPerRow < dayDates.length; daysPerRow++) {
+ if (dayDates[daysPerRow].day() == firstDay) {
+ break;
+ }
+ }
+ rowCnt = Math.ceil(dayDates.length / daysPerRow);
+ }
+ else {
+ rowCnt = 1;
+ daysPerRow = dayDates.length;
+ }
+
+ this.dayDates = dayDates;
+ this.dayIndices = dayIndices;
+ this.daysPerRow = daysPerRow;
+ this.rowCnt = rowCnt;
+
+ this.updateDayTableCols();
+ },
+
+
+ // Computes and assigned the colCnt property and updates any options that may be computed from it
+ updateDayTableCols: function() {
+ this.colCnt = this.computeColCnt();
+ this.colHeadFormat = this.view.opt('columnFormat') || this.computeColHeadFormat();
+ },
+
+
+ // Determines how many columns there should be in the table
+ computeColCnt: function() {
+ return this.daysPerRow;
+ },
+
+
+ // Computes the ambiguously-timed moment for the given cell
+ getCellDate: function(row, col) {
+ return this.dayDates[
+ this.getCellDayIndex(row, col)
+ ].clone();
+ },
+
+
+ // Computes the ambiguously-timed date range for the given cell
+ getCellRange: function(row, col) {
+ var start = this.getCellDate(row, col);
+ var end = start.clone().add(1, 'days');
+
+ return { start: start, end: end };
+ },
+
+
+ // Returns the number of day cells, chronologically, from the first of the grid (0-based)
+ getCellDayIndex: function(row, col) {
+ return row * this.daysPerRow + this.getColDayIndex(col);
+ },
+
+
+ // Returns the numner of day cells, chronologically, from the first cell in *any given row*
+ getColDayIndex: function(col) {
+ if (this.isRTL) {
+ return this.colCnt - 1 - col;
+ }
+ else {
+ return col;
+ }
+ },
+
+
+ // Given a date, returns its chronolocial cell-index from the first cell of the grid.
+ // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets.
+ // If before the first offset, returns a negative number.
+ // If after the last offset, returns an offset past the last cell offset.
+ // Only works for *start* dates of cells. Will not work for exclusive end dates for cells.
+ getDateDayIndex: function(date) {
+ var dayIndices = this.dayIndices;
+ var dayOffset = date.diff(this.start, 'days');
+
+ if (dayOffset < 0) {
+ return dayIndices[0] - 1;
+ }
+ else if (dayOffset >= dayIndices.length) {
+ return dayIndices[dayIndices.length - 1] + 1;
+ }
+ else {
+ return dayIndices[dayOffset];
+ }
+ },
+
+
+ /* Options
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Computes a default column header formatting string if `colFormat` is not explicitly defined
+ computeColHeadFormat: function() {
+ // if more than one week row, or if there are a lot of columns with not much space,
+ // put just the day numbers will be in each cell
+ if (this.rowCnt > 1 || this.colCnt > 10) {
+ return 'ddd'; // "Sat"
+ }
+ // multiple days, so full single date string WON'T be in title text
+ else if (this.colCnt > 1) {
+ return this.view.opt('dayOfMonthFormat'); // "Sat 12/10"
+ }
+ // single day, so full single date string will probably be in title text
+ else {
+ return 'dddd'; // "Saturday"
+ }
+ },
+
+
+ /* Slicing
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Slices up a date range into a segment for every week-row it intersects with
+ sliceRangeByRow: function(range) {
+ var daysPerRow = this.daysPerRow;
+ var normalRange = this.view.computeDayRange(range); // make whole-day range, considering nextDayThreshold
+ var rangeFirst = this.getDateDayIndex(normalRange.start); // inclusive first index
+ var rangeLast = this.getDateDayIndex(normalRange.end.clone().subtract(1, 'days')); // inclusive last index
+ var segs = [];
+ var row;
+ var rowFirst, rowLast; // inclusive day-index range for current row
+ var segFirst, segLast; // inclusive day-index range for segment
+
+ for (row = 0; row < this.rowCnt; row++) {
+ rowFirst = row * daysPerRow;
+ rowLast = rowFirst + daysPerRow - 1;
+
+ // intersect segment's offset range with the row's
+ segFirst = Math.max(rangeFirst, rowFirst);
+ segLast = Math.min(rangeLast, rowLast);
+
+ // deal with in-between indices
+ segFirst = Math.ceil(segFirst); // in-between starts round to next cell
+ segLast = Math.floor(segLast); // in-between ends round to prev cell
+
+ if (segFirst <= segLast) { // was there any intersection with the current row?
+ segs.push({
+ row: row,
+
+ // normalize to start of row
+ firstRowDayIndex: segFirst - rowFirst,
+ lastRowDayIndex: segLast - rowFirst,
+
+ // must be matching integers to be the segment's start/end
+ isStart: segFirst === rangeFirst,
+ isEnd: segLast === rangeLast
+ });
+ }
+ }
+
+ return segs;
+ },
+
+
+ // Slices up a date range into a segment for every day-cell it intersects with.
+ // TODO: make more DRY with sliceRangeByRow somehow.
+ sliceRangeByDay: function(range) {
+ var daysPerRow = this.daysPerRow;
+ var normalRange = this.view.computeDayRange(range); // make whole-day range, considering nextDayThreshold
+ var rangeFirst = this.getDateDayIndex(normalRange.start); // inclusive first index
+ var rangeLast = this.getDateDayIndex(normalRange.end.clone().subtract(1, 'days')); // inclusive last index
+ var segs = [];
+ var row;
+ var rowFirst, rowLast; // inclusive day-index range for current row
+ var i;
+ var segFirst, segLast; // inclusive day-index range for segment
+
+ for (row = 0; row < this.rowCnt; row++) {
+ rowFirst = row * daysPerRow;
+ rowLast = rowFirst + daysPerRow - 1;
+
+ for (i = rowFirst; i <= rowLast; i++) {
+
+ // intersect segment's offset range with the row's
+ segFirst = Math.max(rangeFirst, i);
+ segLast = Math.min(rangeLast, i);
+
+ // deal with in-between indices
+ segFirst = Math.ceil(segFirst); // in-between starts round to next cell
+ segLast = Math.floor(segLast); // in-between ends round to prev cell
+
+ if (segFirst <= segLast) { // was there any intersection with the current row?
+ segs.push({
+ row: row,
+
+ // normalize to start of row
+ firstRowDayIndex: segFirst - rowFirst,
+ lastRowDayIndex: segLast - rowFirst,
+
+ // must be matching integers to be the segment's start/end
+ isStart: segFirst === rangeFirst,
+ isEnd: segLast === rangeLast
+ });
+ }
+ }
+ }
+
+ return segs;
+ },
+
+
+ /* Header Rendering
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderHeadHtml: function() {
+ var view = this.view;
+
+ return '' +
+ '<div class="fc-row ' + view.widgetHeaderClass + '">' +
+ '<table>' +
+ '<thead>' +
+ this.renderHeadTrHtml() +
+ '</thead>' +
+ '</table>' +
+ '</div>';
+ },
+
+
+ renderHeadIntroHtml: function() {
+ return this.renderIntroHtml(); // fall back to generic
+ },
+
+
+ renderHeadTrHtml: function() {
+ return '' +
+ '<tr>' +
+ (this.isRTL ? '' : this.renderHeadIntroHtml()) +
+ this.renderHeadDateCellsHtml() +
+ (this.isRTL ? this.renderHeadIntroHtml() : '') +
+ '</tr>';
+ },
+
+
+ renderHeadDateCellsHtml: function() {
+ var htmls = [];
+ var col, date;
+
+ for (col = 0; col < this.colCnt; col++) {
+ date = this.getCellDate(0, col);
+ htmls.push(this.renderHeadDateCellHtml(date));
+ }
+
+ return htmls.join('');
+ },
+
+
+ // TODO: when internalApiVersion, accept an object for HTML attributes
+ // (colspan should be no different)
+ renderHeadDateCellHtml: function(date, colspan, otherAttrs) {
+ var view = this.view;
+
+ return '' +
+ '<th class="fc-day-header ' + view.widgetHeaderClass + ' fc-' + dayIDs[date.day()] + '"' +
+ (this.rowCnt == 1 ?
+ ' data-date="' + date.format('YYYY-MM-DD') + '"' :
+ '') +
+ (colspan > 1 ?
+ ' colspan="' + colspan + '"' :
+ '') +
+ (otherAttrs ?
+ ' ' + otherAttrs :
+ '') +
+ '>' +
+ htmlEscape(date.format(this.colHeadFormat)) +
+ '</th>';
+ },
+
+
+ /* Background Rendering
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderBgTrHtml: function(row) {
+ return '' +
+ '<tr>' +
+ (this.isRTL ? '' : this.renderBgIntroHtml(row)) +
+ this.renderBgCellsHtml(row) +
+ (this.isRTL ? this.renderBgIntroHtml(row) : '') +
+ '</tr>';
+ },
+
+
+ renderBgIntroHtml: function(row) {
+ return this.renderIntroHtml(); // fall back to generic
+ },
+
+
+ renderBgCellsHtml: function(row) {
+ var htmls = [];
+ var col, date;
+
+ for (col = 0; col < this.colCnt; col++) {
+ date = this.getCellDate(row, col);
+ htmls.push(this.renderBgCellHtml(date));
+ }
+
+ return htmls.join('');
+ },
+
+
+ renderBgCellHtml: function(date, otherAttrs) {
+ var view = this.view;
+ var classes = this.getDayClasses(date);
+
+ classes.unshift('fc-day', view.widgetContentClass);
+
+ return '<td class="' + classes.join(' ') + '"' +
+ ' data-date="' + date.format('YYYY-MM-DD') + '"' + // if date has a time, won't format it
+ (otherAttrs ?
+ ' ' + otherAttrs :
+ '') +
+ '></td>';
+ },
+
+
+ /* Generic
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Generates the default HTML intro for any row. User classes should override
+ renderIntroHtml: function() {
+ },
+
+
+ // TODO: a generic method for dealing with <tr>, RTL, intro
+ // when increment internalApiVersion
+ // wrapTr (scheduler)
+
+
+ /* Utils
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Applies the generic "intro" and "outro" HTML to the given cells.
+ // Intro means the leftmost cell when the calendar is LTR and the rightmost cell when RTL. Vice-versa for outro.
+ bookendCells: function(trEl) {
+ var introHtml = this.renderIntroHtml();
+
+ if (introHtml) {
+ if (this.isRTL) {
+ trEl.append(introHtml);
+ }
+ else {
+ trEl.prepend(introHtml);
+ }
+ }
+ }
+
+};
+
+;;
+
+/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week.
+----------------------------------------------------------------------------------------------------------------------*/
+
+var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
+
+ numbersVisible: false, // should render a row for day/week numbers? set by outside view. TODO: make internal
+ bottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid
+
+ rowEls: null, // set of fake row elements
+ cellEls: null, // set of whole-day elements comprising the row's background
+ helperEls: null, // set of cell skeleton elements for rendering the mock event "helper"
+
+ rowCoordCache: null,
+ colCoordCache: null,
+
+
+ // Renders the rows and columns into the component's `this.el`, which should already be assigned.
+ // isRigid determins whether the individual rows should ignore the contents and be a constant height.
+ // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
+ renderDates: function(isRigid) {
+ var view = this.view;
+ var rowCnt = this.rowCnt;
+ var colCnt = this.colCnt;
+ var html = '';
+ var row;
+ var col;
+
+ for (row = 0; row < rowCnt; row++) {
+ html += this.renderDayRowHtml(row, isRigid);
+ }
+ this.el.html(html);
+
+ this.rowEls = this.el.find('.fc-row');
+ this.cellEls = this.el.find('.fc-day');
+
+ this.rowCoordCache = new CoordCache({
+ els: this.rowEls,
+ isVertical: true
+ });
+ this.colCoordCache = new CoordCache({
+ els: this.cellEls.slice(0, this.colCnt), // only the first row
+ isHorizontal: true
+ });
+
+ // trigger dayRender with each cell's element
+ for (row = 0; row < rowCnt; row++) {
+ for (col = 0; col < colCnt; col++) {
+ view.trigger(
+ 'dayRender',
+ null,
+ this.getCellDate(row, col),
+ this.getCellEl(row, col)
+ );
+ }
+ }
+ },
+
+
+ unrenderDates: function() {
+ this.removeSegPopover();
+ },
+
+
+ renderBusinessHours: function() {
+ var events = this.view.calendar.getBusinessHoursEvents(true); // wholeDay=true
+ var segs = this.eventsToSegs(events);
+
+ this.renderFill('businessHours', segs, 'bgevent');
+ },
+
+
+ // Generates the HTML for a single row, which is a div that wraps a table.
+ // `row` is the row number.
+ renderDayRowHtml: function(row, isRigid) {
+ var view = this.view;
+ var classes = [ 'fc-row', 'fc-week', view.widgetContentClass ];
+
+ if (isRigid) {
+ classes.push('fc-rigid');
+ }
+
+ return '' +
+ '<div class="' + classes.join(' ') + '">' +
+ '<div class="fc-bg">' +
+ '<table>' +
+ this.renderBgTrHtml(row) +
+ '</table>' +
+ '</div>' +
+ '<div class="fc-content-skeleton">' +
+ '<table>' +
+ (this.numbersVisible ?
+ '<thead>' +
+ this.renderNumberTrHtml(row) +
+ '</thead>' :
+ ''
+ ) +
+ '</table>' +
+ '</div>' +
+ '</div>';
+ },
+
+
+ /* Grid Number Rendering
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderNumberTrHtml: function(row) {
+ return '' +
+ '<tr>' +
+ (this.isRTL ? '' : this.renderNumberIntroHtml(row)) +
+ this.renderNumberCellsHtml(row) +
+ (this.isRTL ? this.renderNumberIntroHtml(row) : '') +
+ '</tr>';
+ },
+
+
+ renderNumberIntroHtml: function(row) {
+ return this.renderIntroHtml();
+ },
+
+
+ renderNumberCellsHtml: function(row) {
+ var htmls = [];
+ var col, date;
+
+ for (col = 0; col < this.colCnt; col++) {
+ date = this.getCellDate(row, col);
+ htmls.push(this.renderNumberCellHtml(date));
+ }
+
+ return htmls.join('');
+ },
+
+
+ // Generates the HTML for the <td>s of the "number" row in the DayGrid's content skeleton.
+ // The number row will only exist if either day numbers or week numbers are turned on.
+ renderNumberCellHtml: function(date) {
+ var classes;
+
+ if (!this.view.dayNumbersVisible) { // if there are week numbers but not day numbers
+ return '<td/>'; // will create an empty space above events :(
+ }
+
+ classes = this.getDayClasses(date);
+ classes.unshift('fc-day-number');
+
+ return '' +
+ '<td class="' + classes.join(' ') + '" data-date="' + date.format() + '">' +
+ date.date() +
+ '</td>';
+ },
+
+
+ /* Options
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Computes a default event time formatting string if `timeFormat` is not explicitly defined
+ computeEventTimeFormat: function() {
+ return this.view.opt('extraSmallTimeFormat'); // like "6p" or "6:30p"
+ },
+
+
+ // Computes a default `displayEventEnd` value if one is not expliclty defined
+ computeDisplayEventEnd: function() {
+ return this.colCnt == 1; // we'll likely have space if there's only one day
+ },
+
+
+ /* Dates
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ rangeUpdated: function() {
+ this.updateDayTable();
+ },
+
+
+ // Slices up the given span (unzoned start/end with other misc data) into an array of segments
+ spanToSegs: function(span) {
+ var segs = this.sliceRangeByRow(span);
+ var i, seg;
+
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ if (this.isRTL) {
+ seg.leftCol = this.daysPerRow - 1 - seg.lastRowDayIndex;
+ seg.rightCol = this.daysPerRow - 1 - seg.firstRowDayIndex;
+ }
+ else {
+ seg.leftCol = seg.firstRowDayIndex;
+ seg.rightCol = seg.lastRowDayIndex;
+ }
+ }
+
+ return segs;
+ },
+
+
+ /* Hit System
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ prepareHits: function() {
+ this.colCoordCache.build();
+ this.rowCoordCache.build();
+ this.rowCoordCache.bottoms[this.rowCnt - 1] += this.bottomCoordPadding; // hack
+ },
+
+
+ releaseHits: function() {
+ this.colCoordCache.clear();
+ this.rowCoordCache.clear();
+ },
+
+
+ queryHit: function(leftOffset, topOffset) {
+ var col = this.colCoordCache.getHorizontalIndex(leftOffset);
+ var row = this.rowCoordCache.getVerticalIndex(topOffset);
+
+ if (row != null && col != null) {
+ return this.getCellHit(row, col);
+ }
+ },
+
+
+ getHitSpan: function(hit) {
+ return this.getCellRange(hit.row, hit.col);
+ },
+
+
+ getHitEl: function(hit) {
+ return this.getCellEl(hit.row, hit.col);
+ },
+
+
+ /* Cell System
+ ------------------------------------------------------------------------------------------------------------------*/
+ // FYI: the first column is the leftmost column, regardless of date
+
+
+ getCellHit: function(row, col) {
+ return {
+ row: row,
+ col: col,
+ component: this, // needed unfortunately :(
+ left: this.colCoordCache.getLeftOffset(col),
+ right: this.colCoordCache.getRightOffset(col),
+ top: this.rowCoordCache.getTopOffset(row),
+ bottom: this.rowCoordCache.getBottomOffset(row)
+ };
+ },
+
+
+ getCellEl: function(row, col) {
+ return this.cellEls.eq(row * this.colCnt + col);
+ },
+
+
+ /* Event Drag Visualization
+ ------------------------------------------------------------------------------------------------------------------*/
+ // TODO: move to DayGrid.event, similar to what we did with Grid's drag methods
+
+
+ // Renders a visual indication of an event or external element being dragged.
+ // `eventLocation` has zoned start and end (optional)
+ renderDrag: function(eventLocation, seg) {
+
+ // always render a highlight underneath
+ this.renderHighlight(this.eventToSpan(eventLocation));
+
+ // if a segment from the same calendar but another component is being dragged, render a helper event
+ if (seg && !seg.el.closest(this.el).length) {
+
+ return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
+ }
+ },
+
+
+ // Unrenders any visual indication of a hovering event
+ unrenderDrag: function() {
+ this.unrenderHighlight();
+ this.unrenderHelper();
+ },
+
+
+ /* Event Resize Visualization
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of an event being resized
+ renderEventResize: function(eventLocation, seg) {
+ this.renderHighlight(this.eventToSpan(eventLocation));
+ return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
+ },
+
+
+ // Unrenders a visual indication of an event being resized
+ unrenderEventResize: function() {
+ this.unrenderHighlight();
+ this.unrenderHelper();
+ },
+
+
+ /* Event Helper
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null.
+ renderHelper: function(event, sourceSeg) {
+ var helperNodes = [];
+ var segs = this.eventToSegs(event);
+ var rowStructs;
+
+ segs = this.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered
+ rowStructs = this.renderSegRows(segs);
+
+ // inject each new event skeleton into each associated row
+ this.rowEls.each(function(row, rowNode) {
+ var rowEl = $(rowNode); // the .fc-row
+ var skeletonEl = $('<div class="fc-helper-skeleton"><table/></div>'); // will be absolutely positioned
+ var skeletonTop;
+
+ // If there is an original segment, match the top position. Otherwise, put it at the row's top level
+ if (sourceSeg && sourceSeg.row === row) {
+ skeletonTop = sourceSeg.el.position().top;
+ }
+ else {
+ skeletonTop = rowEl.find('.fc-content-skeleton tbody').position().top;
+ }
+
+ skeletonEl.css('top', skeletonTop)
+ .find('table')
+ .append(rowStructs[row].tbodyEl);
+
+ rowEl.append(skeletonEl);
+ helperNodes.push(skeletonEl[0]);
+ });
+
+ return ( // must return the elements rendered
+ this.helperEls = $(helperNodes) // array -> jQuery set
+ );
+ },
+
+
+ // Unrenders any visual indication of a mock helper event
+ unrenderHelper: function() {
+ if (this.helperEls) {
+ this.helperEls.remove();
+ this.helperEls = null;
+ }
+ },
+
+
+ /* Fill System (highlight, background events, business hours)
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ fillSegTag: 'td', // override the default tag name
+
+
+ // Renders a set of rectangles over the given segments of days.
+ // Only returns segments that successfully rendered.
+ renderFill: function(type, segs, className) {
+ var nodes = [];
+ var i, seg;
+ var skeletonEl;
+
+ segs = this.renderFillSegEls(type, segs); // assignes `.el` to each seg. returns successfully rendered segs
+
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ skeletonEl = this.renderFillRow(type, seg, className);
+ this.rowEls.eq(seg.row).append(skeletonEl);
+ nodes.push(skeletonEl[0]);
+ }
+
+ this.elsByFill[type] = $(nodes);
+
+ return segs;
+ },
+
+
+ // Generates the HTML needed for one row of a fill. Requires the seg's el to be rendered.
+ renderFillRow: function(type, seg, className) {
+ var colCnt = this.colCnt;
+ var startCol = seg.leftCol;
+ var endCol = seg.rightCol + 1;
+ var skeletonEl;
+ var trEl;
+
+ className = className || type.toLowerCase();
+
+ skeletonEl = $(
+ '<div class="fc-' + className + '-skeleton">' +
+ '<table><tr/></table>' +
+ '</div>'
+ );
+ trEl = skeletonEl.find('tr');
+
+ if (startCol > 0) {
+ trEl.append('<td colspan="' + startCol + '"/>');
+ }
+
+ trEl.append(
+ seg.el.attr('colspan', endCol - startCol)
+ );
+
+ if (endCol < colCnt) {
+ trEl.append('<td colspan="' + (colCnt - endCol) + '"/>');
+ }
+
+ this.bookendCells(trEl);
+
+ return skeletonEl;
+ }
+
+});
+
+;;
+
+/* Event-rendering methods for the DayGrid class
+----------------------------------------------------------------------------------------------------------------------*/
+
+DayGrid.mixin({
+
+ rowStructs: null, // an array of objects, each holding information about a row's foreground event-rendering
+
+
+ // Unrenders all events currently rendered on the grid
+ unrenderEvents: function() {
+ this.removeSegPopover(); // removes the "more.." events popover
+ Grid.prototype.unrenderEvents.apply(this, arguments); // calls the super-method
+ },
+
+
+ // Retrieves all rendered segment objects currently rendered on the grid
+ getEventSegs: function() {
+ return Grid.prototype.getEventSegs.call(this) // get the segments from the super-method
+ .concat(this.popoverSegs || []); // append the segments from the "more..." popover
+ },
+
+
+ // Renders the given background event segments onto the grid
+ renderBgSegs: function(segs) {
+
+ // don't render timed background events
+ var allDaySegs = $.grep(segs, function(seg) {
+ return seg.event.allDay;
+ });
+
+ return Grid.prototype.renderBgSegs.call(this, allDaySegs); // call the super-method
+ },
+
+
+ // Renders the given foreground event segments onto the grid
+ renderFgSegs: function(segs) {
+ var rowStructs;
+
+ // render an `.el` on each seg
+ // returns a subset of the segs. segs that were actually rendered
+ segs = this.renderFgSegEls(segs);
+
+ rowStructs = this.rowStructs = this.renderSegRows(segs);
+
+ // append to each row's content skeleton
+ this.rowEls.each(function(i, rowNode) {
+ $(rowNode).find('.fc-content-skeleton > table').append(
+ rowStructs[i].tbodyEl
+ );
+ });
+
+ return segs; // return only the segs that were actually rendered
+ },
+
+
+ // Unrenders all currently rendered foreground event segments
+ unrenderFgSegs: function() {
+ var rowStructs = this.rowStructs || [];
+ var rowStruct;
+
+ while ((rowStruct = rowStructs.pop())) {
+ rowStruct.tbodyEl.remove();
+ }
+
+ this.rowStructs = null;
+ },
+
+
+ // Uses the given events array to generate <tbody> elements that should be appended to each row's content skeleton.
+ // Returns an array of rowStruct objects (see the bottom of `renderSegRow`).
+ // PRECONDITION: each segment shoud already have a rendered and assigned `.el`
+ renderSegRows: function(segs) {
+ var rowStructs = [];
+ var segRows;
+ var row;
+
+ segRows = this.groupSegRows(segs); // group into nested arrays
+
+ // iterate each row of segment groupings
+ for (row = 0; row < segRows.length; row++) {
+ rowStructs.push(
+ this.renderSegRow(row, segRows[row])
+ );
+ }
+
+ return rowStructs;
+ },
+
+
+ // Builds the HTML to be used for the default element for an individual segment
+ fgSegHtml: function(seg, disableResizing) {
+ var view = this.view;
+ var event = seg.event;
+ var isDraggable = view.isEventDraggable(event);
+ var isResizableFromStart = !disableResizing && event.allDay &&
+ seg.isStart && view.isEventResizableFromStart(event);
+ var isResizableFromEnd = !disableResizing && event.allDay &&
+ seg.isEnd && view.isEventResizableFromEnd(event);
+ var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd);
+ var skinCss = cssToStr(this.getSegSkinCss(seg));
+ var timeHtml = '';
+ var timeText;
+ var titleHtml;
+
+ classes.unshift('fc-day-grid-event', 'fc-h-event');
+
+ // Only display a timed events time if it is the starting segment
+ if (seg.isStart) {
+ timeText = this.getEventTimeText(event);
+ if (timeText) {
+ timeHtml = '<span class="fc-time">' + htmlEscape(timeText) + '</span>';
+ }
+ }
+
+ titleHtml =
+ '<span class="fc-title">' +
+ (htmlEscape(event.title || '') || '&nbsp;') + // we always want one line of height
+ '</span>';
+
+ return '<a class="' + classes.join(' ') + '"' +
+ (event.url ?
+ ' href="' + htmlEscape(event.url) + '"' :
+ ''
+ ) +
+ (skinCss ?
+ ' style="' + skinCss + '"' :
+ ''
+ ) +
+ '>' +
+ '<div class="fc-content">' +
+ (this.isRTL ?
+ titleHtml + ' ' + timeHtml : // put a natural space in between
+ timeHtml + ' ' + titleHtml //
+ ) +
+ '</div>' +
+ (isResizableFromStart ?
+ '<div class="fc-resizer fc-start-resizer" />' :
+ ''
+ ) +
+ (isResizableFromEnd ?
+ '<div class="fc-resizer fc-end-resizer" />' :
+ ''
+ ) +
+ '</a>';
+ },
+
+
+ // Given a row # and an array of segments all in the same row, render a <tbody> element, a skeleton that contains
+ // the segments. Returns object with a bunch of internal data about how the render was calculated.
+ // NOTE: modifies rowSegs
+ renderSegRow: function(row, rowSegs) {
+ var colCnt = this.colCnt;
+ var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels
+ var levelCnt = Math.max(1, segLevels.length); // ensure at least one level
+ var tbody = $('<tbody/>');
+ var segMatrix = []; // lookup for which segments are rendered into which level+col cells
+ var cellMatrix = []; // lookup for all <td> elements of the level+col matrix
+ var loneCellMatrix = []; // lookup for <td> elements that only take up a single column
+ var i, levelSegs;
+ var col;
+ var tr;
+ var j, seg;
+ var td;
+
+ // populates empty cells from the current column (`col`) to `endCol`
+ function emptyCellsUntil(endCol) {
+ while (col < endCol) {
+ // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell
+ td = (loneCellMatrix[i - 1] || [])[col];
+ if (td) {
+ td.attr(
+ 'rowspan',
+ parseInt(td.attr('rowspan') || 1, 10) + 1
+ );
+ }
+ else {
+ td = $('<td/>');
+ tr.append(td);
+ }
+ cellMatrix[i][col] = td;
+ loneCellMatrix[i][col] = td;
+ col++;
+ }
+ }
+
+ for (i = 0; i < levelCnt; i++) { // iterate through all levels
+ levelSegs = segLevels[i];
+ col = 0;
+ tr = $('<tr/>');
+
+ segMatrix.push([]);
+ cellMatrix.push([]);
+ loneCellMatrix.push([]);
+
+ // levelCnt might be 1 even though there are no actual levels. protect against this.
+ // this single empty row is useful for styling.
+ if (levelSegs) {
+ for (j = 0; j < levelSegs.length; j++) { // iterate through segments in level
+ seg = levelSegs[j];
+
+ emptyCellsUntil(seg.leftCol);
+
+ // create a container that occupies or more columns. append the event element.
+ td = $('<td class="fc-event-container"/>').append(seg.el);
+ if (seg.leftCol != seg.rightCol) {
+ td.attr('colspan', seg.rightCol - seg.leftCol + 1);
+ }
+ else { // a single-column segment
+ loneCellMatrix[i][col] = td;
+ }
+
+ while (col <= seg.rightCol) {
+ cellMatrix[i][col] = td;
+ segMatrix[i][col] = seg;
+ col++;
+ }
+
+ tr.append(td);
+ }
+ }
+
+ emptyCellsUntil(colCnt); // finish off the row
+ this.bookendCells(tr);
+ tbody.append(tr);
+ }
+
+ return { // a "rowStruct"
+ row: row, // the row number
+ tbodyEl: tbody,
+ cellMatrix: cellMatrix,
+ segMatrix: segMatrix,
+ segLevels: segLevels,
+ segs: rowSegs
+ };
+ },
+
+
+ // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels.
+ // NOTE: modifies segs
+ buildSegLevels: function(segs) {
+ var levels = [];
+ var i, seg;
+ var j;
+
+ // Give preference to elements with certain criteria, so they have
+ // a chance to be closer to the top.
+ this.sortEventSegs(segs);
+
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+
+ // loop through levels, starting with the topmost, until the segment doesn't collide with other segments
+ for (j = 0; j < levels.length; j++) {
+ if (!isDaySegCollision(seg, levels[j])) {
+ break;
+ }
+ }
+ // `j` now holds the desired subrow index
+ seg.level = j;
+
+ // create new level array if needed and append segment
+ (levels[j] || (levels[j] = [])).push(seg);
+ }
+
+ // order segments left-to-right. very important if calendar is RTL
+ for (j = 0; j < levels.length; j++) {
+ levels[j].sort(compareDaySegCols);
+ }
+
+ return levels;
+ },
+
+
+ // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row
+ groupSegRows: function(segs) {
+ var segRows = [];
+ var i;
+
+ for (i = 0; i < this.rowCnt; i++) {
+ segRows.push([]);
+ }
+
+ for (i = 0; i < segs.length; i++) {
+ segRows[segs[i].row].push(segs[i]);
+ }
+
+ return segRows;
+ }
+
+});
+
+
+// Computes whether two segments' columns collide. They are assumed to be in the same row.
+function isDaySegCollision(seg, otherSegs) {
+ var i, otherSeg;
+
+ for (i = 0; i < otherSegs.length; i++) {
+ otherSeg = otherSegs[i];
+
+ if (
+ otherSeg.leftCol <= seg.rightCol &&
+ otherSeg.rightCol >= seg.leftCol
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+// A cmp function for determining the leftmost event
+function compareDaySegCols(a, b) {
+ return a.leftCol - b.leftCol;
+}
+
+;;
+
+/* Methods relate to limiting the number events for a given day on a DayGrid
+----------------------------------------------------------------------------------------------------------------------*/
+// NOTE: all the segs being passed around in here are foreground segs
+
+DayGrid.mixin({
+
+ segPopover: null, // the Popover that holds events that can't fit in a cell. null when not visible
+ popoverSegs: null, // an array of segment objects that the segPopover holds. null when not visible
+
+
+ removeSegPopover: function() {
+ if (this.segPopover) {
+ this.segPopover.hide(); // in handler, will call segPopover's removeElement
+ }
+ },
+
+
+ // Limits the number of "levels" (vertically stacking layers of events) for each row of the grid.
+ // `levelLimit` can be false (don't limit), a number, or true (should be computed).
+ limitRows: function(levelLimit) {
+ var rowStructs = this.rowStructs || [];
+ var row; // row #
+ var rowLevelLimit;
+
+ for (row = 0; row < rowStructs.length; row++) {
+ this.unlimitRow(row);
+
+ if (!levelLimit) {
+ rowLevelLimit = false;
+ }
+ else if (typeof levelLimit === 'number') {
+ rowLevelLimit = levelLimit;
+ }
+ else {
+ rowLevelLimit = this.computeRowLevelLimit(row);
+ }
+
+ if (rowLevelLimit !== false) {
+ this.limitRow(row, rowLevelLimit);
+ }
+ }
+ },
+
+
+ // Computes the number of levels a row will accomodate without going outside its bounds.
+ // Assumes the row is "rigid" (maintains a constant height regardless of what is inside).
+ // `row` is the row number.
+ computeRowLevelLimit: function(row) {
+ var rowEl = this.rowEls.eq(row); // the containing "fake" row div
+ var rowHeight = rowEl.height(); // TODO: cache somehow?
+ var trEls = this.rowStructs[row].tbodyEl.children();
+ var i, trEl;
+ var trHeight;
+
+ function iterInnerHeights(i, childNode) {
+ trHeight = Math.max(trHeight, $(childNode).outerHeight());
+ }
+
+ // Reveal one level <tr> at a time and stop when we find one out of bounds
+ for (i = 0; i < trEls.length; i++) {
+ trEl = trEls.eq(i).removeClass('fc-limited'); // reset to original state (reveal)
+
+ // with rowspans>1 and IE8, trEl.outerHeight() would return the height of the largest cell,
+ // so instead, find the tallest inner content element.
+ trHeight = 0;
+ trEl.find('> td > :first-child').each(iterInnerHeights);
+
+ if (trEl.position().top + trHeight > rowHeight) {
+ return i;
+ }
+ }
+
+ return false; // should not limit at all
+ },
+
+
+ // Limits the given grid row to the maximum number of levels and injects "more" links if necessary.
+ // `row` is the row number.
+ // `levelLimit` is a number for the maximum (inclusive) number of levels allowed.
+ limitRow: function(row, levelLimit) {
+ var _this = this;
+ var rowStruct = this.rowStructs[row];
+ var moreNodes = []; // array of "more" <a> links and <td> DOM nodes
+ var col = 0; // col #, left-to-right (not chronologically)
+ var levelSegs; // array of segment objects in the last allowable level, ordered left-to-right
+ var cellMatrix; // a matrix (by level, then column) of all <td> jQuery elements in the row
+ var limitedNodes; // array of temporarily hidden level <tr> and segment <td> DOM nodes
+ var i, seg;
+ var segsBelow; // array of segment objects below `seg` in the current `col`
+ var totalSegsBelow; // total number of segments below `seg` in any of the columns `seg` occupies
+ var colSegsBelow; // array of segment arrays, below seg, one for each column (offset from segs's first column)
+ var td, rowspan;
+ var segMoreNodes; // array of "more" <td> cells that will stand-in for the current seg's cell
+ var j;
+ var moreTd, moreWrap, moreLink;
+
+ // Iterates through empty level cells and places "more" links inside if need be
+ function emptyCellsUntil(endCol) { // goes from current `col` to `endCol`
+ while (col < endCol) {
+ segsBelow = _this.getCellSegs(row, col, levelLimit);
+ if (segsBelow.length) {
+ td = cellMatrix[levelLimit - 1][col];
+ moreLink = _this.renderMoreLink(row, col, segsBelow);
+ moreWrap = $('<div/>').append(moreLink);
+ td.append(moreWrap);
+ moreNodes.push(moreWrap[0]);
+ }
+ col++;
+ }
+ }
+
+ if (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit?
+ levelSegs = rowStruct.segLevels[levelLimit - 1];
+ cellMatrix = rowStruct.cellMatrix;
+
+ limitedNodes = rowStruct.tbodyEl.children().slice(levelLimit) // get level <tr> elements past the limit
+ .addClass('fc-limited').get(); // hide elements and get a simple DOM-nodes array
+
+ // iterate though segments in the last allowable level
+ for (i = 0; i < levelSegs.length; i++) {
+ seg = levelSegs[i];
+ emptyCellsUntil(seg.leftCol); // process empty cells before the segment
+
+ // determine *all* segments below `seg` that occupy the same columns
+ colSegsBelow = [];
+ totalSegsBelow = 0;
+ while (col <= seg.rightCol) {
+ segsBelow = this.getCellSegs(row, col, levelLimit);
+ colSegsBelow.push(segsBelow);
+ totalSegsBelow += segsBelow.length;
+ col++;
+ }
+
+ if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links?
+ td = cellMatrix[levelLimit - 1][seg.leftCol]; // the segment's parent cell
+ rowspan = td.attr('rowspan') || 1;
+ segMoreNodes = [];
+
+ // make a replacement <td> for each column the segment occupies. will be one for each colspan
+ for (j = 0; j < colSegsBelow.length; j++) {
+ moreTd = $('<td class="fc-more-cell"/>').attr('rowspan', rowspan);
+ segsBelow = colSegsBelow[j];
+ moreLink = this.renderMoreLink(
+ row,
+ seg.leftCol + j,
+ [ seg ].concat(segsBelow) // count seg as hidden too
+ );
+ moreWrap = $('<div/>').append(moreLink);
+ moreTd.append(moreWrap);
+ segMoreNodes.push(moreTd[0]);
+ moreNodes.push(moreTd[0]);
+ }
+
+ td.addClass('fc-limited').after($(segMoreNodes)); // hide original <td> and inject replacements
+ limitedNodes.push(td[0]);
+ }
+ }
+
+ emptyCellsUntil(this.colCnt); // finish off the level
+ rowStruct.moreEls = $(moreNodes); // for easy undoing later
+ rowStruct.limitedEls = $(limitedNodes); // for easy undoing later
+ }
+ },
+
+
+ // Reveals all levels and removes all "more"-related elements for a grid's row.
+ // `row` is a row number.
+ unlimitRow: function(row) {
+ var rowStruct = this.rowStructs[row];
+
+ if (rowStruct.moreEls) {
+ rowStruct.moreEls.remove();
+ rowStruct.moreEls = null;
+ }
+
+ if (rowStruct.limitedEls) {
+ rowStruct.limitedEls.removeClass('fc-limited');
+ rowStruct.limitedEls = null;
+ }
+ },
+
+
+ // Renders an <a> element that represents hidden event element for a cell.
+ // Responsible for attaching click handler as well.
+ renderMoreLink: function(row, col, hiddenSegs) {
+ var _this = this;
+ var view = this.view;
+
+ return $('<a class="fc-more"/>')
+ .text(
+ this.getMoreLinkText(hiddenSegs.length)
+ )
+ .on('click', function(ev) {
+ var clickOption = view.opt('eventLimitClick');
+ var date = _this.getCellDate(row, col);
+ var moreEl = $(this);
+ var dayEl = _this.getCellEl(row, col);
+ var allSegs = _this.getCellSegs(row, col);
+
+ // rescope the segments to be within the cell's date
+ var reslicedAllSegs = _this.resliceDaySegs(allSegs, date);
+ var reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date);
+
+ if (typeof clickOption === 'function') {
+ // the returned value can be an atomic option
+ clickOption = view.trigger('eventLimitClick', null, {
+ date: date,
+ dayEl: dayEl,
+ moreEl: moreEl,
+ segs: reslicedAllSegs,
+ hiddenSegs: reslicedHiddenSegs
+ }, ev);
+ }
+
+ if (clickOption === 'popover') {
+ _this.showSegPopover(row, col, moreEl, reslicedAllSegs);
+ }
+ else if (typeof clickOption === 'string') { // a view name
+ view.calendar.zoomTo(date, clickOption);
+ }
+ });
+ },
+
+
+ // Reveals the popover that displays all events within a cell
+ showSegPopover: function(row, col, moreLink, segs) {
+ var _this = this;
+ var view = this.view;
+ var moreWrap = moreLink.parent(); // the <div> wrapper around the <a>
+ var topEl; // the element we want to match the top coordinate of
+ var options;
+
+ if (this.rowCnt == 1) {
+ topEl = view.el; // will cause the popover to cover any sort of header
+ }
+ else {
+ topEl = this.rowEls.eq(row); // will align with top of row
+ }
+
+ options = {
+ className: 'fc-more-popover',
+ content: this.renderSegPopoverContent(row, col, segs),
+ parentEl: this.el,
+ top: topEl.offset().top,
+ autoHide: true, // when the user clicks elsewhere, hide the popover
+ viewportConstrain: view.opt('popoverViewportConstrain'),
+ hide: function() {
+ // kill everything when the popover is hidden
+ _this.segPopover.removeElement();
+ _this.segPopover = null;
+ _this.popoverSegs = null;
+ }
+ };
+
+ // Determine horizontal coordinate.
+ // We use the moreWrap instead of the <td> to avoid border confusion.
+ if (this.isRTL) {
+ options.right = moreWrap.offset().left + moreWrap.outerWidth() + 1; // +1 to be over cell border
+ }
+ else {
+ options.left = moreWrap.offset().left - 1; // -1 to be over cell border
+ }
+
+ this.segPopover = new Popover(options);
+ this.segPopover.show();
+ },
+
+
+ // Builds the inner DOM contents of the segment popover
+ renderSegPopoverContent: function(row, col, segs) {
+ var view = this.view;
+ var isTheme = view.opt('theme');
+ var title = this.getCellDate(row, col).format(view.opt('dayPopoverFormat'));
+ var content = $(
+ '<div class="fc-header ' + view.widgetHeaderClass + '">' +
+ '<span class="fc-close ' +
+ (isTheme ? 'ui-icon ui-icon-closethick' : 'fc-icon fc-icon-x') +
+ '"></span>' +
+ '<span class="fc-title">' +
+ htmlEscape(title) +
+ '</span>' +
+ '<div class="fc-clear"/>' +
+ '</div>' +
+ '<div class="fc-body ' + view.widgetContentClass + '">' +
+ '<div class="fc-event-container"></div>' +
+ '</div>'
+ );
+ var segContainer = content.find('.fc-event-container');
+ var i;
+
+ // render each seg's `el` and only return the visible segs
+ segs = this.renderFgSegEls(segs, true); // disableResizing=true
+ this.popoverSegs = segs;
+
+ for (i = 0; i < segs.length; i++) {
+
+ // because segments in the popover are not part of a grid coordinate system, provide a hint to any
+ // grids that want to do drag-n-drop about which cell it came from
+ this.prepareHits();
+ segs[i].hit = this.getCellHit(row, col);
+ this.releaseHits();
+
+ segContainer.append(segs[i].el);
+ }
+
+ return content;
+ },
+
+
+ // Given the events within an array of segment objects, reslice them to be in a single day
+ resliceDaySegs: function(segs, dayDate) {
+
+ // build an array of the original events
+ var events = $.map(segs, function(seg) {
+ return seg.event;
+ });
+
+ var dayStart = dayDate.clone();
+ var dayEnd = dayStart.clone().add(1, 'days');
+ var dayRange = { start: dayStart, end: dayEnd };
+
+ // slice the events with a custom slicing function
+ segs = this.eventsToSegs(
+ events,
+ function(range) {
+ var seg = intersectRanges(range, dayRange); // undefind if no intersection
+ return seg ? [ seg ] : []; // must return an array of segments
+ }
+ );
+
+ // force an order because eventsToSegs doesn't guarantee one
+ this.sortEventSegs(segs);
+
+ return segs;
+ },
+
+
+ // Generates the text that should be inside a "more" link, given the number of events it represents
+ getMoreLinkText: function(num) {
+ var opt = this.view.opt('eventLimitText');
+
+ if (typeof opt === 'function') {
+ return opt(num);
+ }
+ else {
+ return '+' + num + ' ' + opt;
+ }
+ },
+
+
+ // Returns segments within a given cell.
+ // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs.
+ getCellSegs: function(row, col, startLevel) {
+ var segMatrix = this.rowStructs[row].segMatrix;
+ var level = startLevel || 0;
+ var segs = [];
+ var seg;
+
+ while (level < segMatrix.length) {
+ seg = segMatrix[level][col];
+ if (seg) {
+ segs.push(seg);
+ }
+ level++;
+ }
+
+ return segs;
+ }
+
+});
+
+;;
+
+/* A component that renders one or more columns of vertical time slots
+----------------------------------------------------------------------------------------------------------------------*/
+// We mixin DayTable, even though there is only a single row of days
+
+var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
+
+ slotDuration: null, // duration of a "slot", a distinct time segment on given day, visualized by lines
+ snapDuration: null, // granularity of time for dragging and selecting
+ snapsPerSlot: null,
+ minTime: null, // Duration object that denotes the first visible time of any given day
+ maxTime: null, // Duration object that denotes the exclusive visible end time of any given day
+ labelFormat: null, // formatting string for times running along vertical axis
+ labelInterval: null, // duration of how often a label should be displayed for a slot
+
+ colEls: null, // cells elements in the day-row background
+ slatContainerEl: null, // div that wraps all the slat rows
+ slatEls: null, // elements running horizontally across all columns
+ nowIndicatorEls: null,
+
+ colCoordCache: null,
+ slatCoordCache: null,
+
+
+ constructor: function() {
+ Grid.apply(this, arguments); // call the super-constructor
+
+ this.processOptions();
+ },
+
+
+ // Renders the time grid into `this.el`, which should already be assigned.
+ // Relies on the view's colCnt. In the future, this component should probably be self-sufficient.
+ renderDates: function() {
+ this.el.html(this.renderHtml());
+ this.colEls = this.el.find('.fc-day');
+ this.slatContainerEl = this.el.find('.fc-slats');
+ this.slatEls = this.slatContainerEl.find('tr');
+
+ this.colCoordCache = new CoordCache({
+ els: this.colEls,
+ isHorizontal: true
+ });
+ this.slatCoordCache = new CoordCache({
+ els: this.slatEls,
+ isVertical: true
+ });
+
+ this.renderContentSkeleton();
+ },
+
+
+ // Renders the basic HTML skeleton for the grid
+ renderHtml: function() {
+ return '' +
+ '<div class="fc-bg">' +
+ '<table>' +
+ this.renderBgTrHtml(0) + // row=0
+ '</table>' +
+ '</div>' +
+ '<div class="fc-slats">' +
+ '<table>' +
+ this.renderSlatRowHtml() +
+ '</table>' +
+ '</div>';
+ },
+
+
+ // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL.
+ renderSlatRowHtml: function() {
+ var view = this.view;
+ var isRTL = this.isRTL;
+ var html = '';
+ var slotTime = moment.duration(+this.minTime); // wish there was .clone() for durations
+ var slotDate; // will be on the view's first day, but we only care about its time
+ var isLabeled;
+ var axisHtml;
+
+ // Calculate the time for each slot
+ while (slotTime < this.maxTime) {
+ slotDate = this.start.clone().time(slotTime);
+ isLabeled = isInt(divideDurationByDuration(slotTime, this.labelInterval));
+
+ axisHtml =
+ '<td class="fc-axis fc-time ' + view.widgetContentClass + '" ' + view.axisStyleAttr() + '>' +
+ (isLabeled ?
+ '<span>' + // for matchCellWidths
+ htmlEscape(slotDate.format(this.labelFormat)) +
+ '</span>' :
+ ''
+ ) +
+ '</td>';
+
+ html +=
+ '<tr data-time="' + slotDate.format('HH:mm:ss') + '"' +
+ (isLabeled ? '' : ' class="fc-minor"') +
+ '>' +
+ (!isRTL ? axisHtml : '') +
+ '<td class="' + view.widgetContentClass + '"/>' +
+ (isRTL ? axisHtml : '') +
+ "</tr>";
+
+ slotTime.add(this.slotDuration);
+ }
+
+ return html;
+ },
+
+
+ /* Options
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Parses various options into properties of this object
+ processOptions: function() {
+ var view = this.view;
+ var slotDuration = view.opt('slotDuration');
+ var snapDuration = view.opt('snapDuration');
+ var input;
+
+ slotDuration = moment.duration(slotDuration);
+ snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration;
+
+ this.slotDuration = slotDuration;
+ this.snapDuration = snapDuration;
+ this.snapsPerSlot = slotDuration / snapDuration; // TODO: ensure an integer multiple?
+
+ this.minResizeDuration = snapDuration; // hack
+
+ this.minTime = moment.duration(view.opt('minTime'));
+ this.maxTime = moment.duration(view.opt('maxTime'));
+
+ // might be an array value (for TimelineView).
+ // if so, getting the most granular entry (the last one probably).
+ input = view.opt('slotLabelFormat');
+ if ($.isArray(input)) {
+ input = input[input.length - 1];
+ }
+
+ this.labelFormat =
+ input ||
+ view.opt('axisFormat') || // deprecated
+ view.opt('smallTimeFormat'); // the computed default
+
+ input = view.opt('slotLabelInterval');
+ this.labelInterval = input ?
+ moment.duration(input) :
+ this.computeLabelInterval(slotDuration);
+ },
+
+
+ // Computes an automatic value for slotLabelInterval
+ computeLabelInterval: function(slotDuration) {
+ var i;
+ var labelInterval;
+ var slotsPerLabel;
+
+ // find the smallest stock label interval that results in more than one slots-per-label
+ for (i = AGENDA_STOCK_SUB_DURATIONS.length - 1; i >= 0; i--) {
+ labelInterval = moment.duration(AGENDA_STOCK_SUB_DURATIONS[i]);
+ slotsPerLabel = divideDurationByDuration(labelInterval, slotDuration);
+ if (isInt(slotsPerLabel) && slotsPerLabel > 1) {
+ return labelInterval;
+ }
+ }
+
+ return moment.duration(slotDuration); // fall back. clone
+ },
+
+
+ // Computes a default event time formatting string if `timeFormat` is not explicitly defined
+ computeEventTimeFormat: function() {
+ return this.view.opt('noMeridiemTimeFormat'); // like "6:30" (no AM/PM)
+ },
+
+
+ // Computes a default `displayEventEnd` value if one is not expliclty defined
+ computeDisplayEventEnd: function() {
+ return true;
+ },
+
+
+ /* Hit System
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ prepareHits: function() {
+ this.colCoordCache.build();
+ this.slatCoordCache.build();
+ },
+
+
+ releaseHits: function() {
+ this.colCoordCache.clear();
+ // NOTE: don't clear slatCoordCache because we rely on it for computeTimeTop
+ },
+
+
+ queryHit: function(leftOffset, topOffset) {
+ var snapsPerSlot = this.snapsPerSlot;
+ var colCoordCache = this.colCoordCache;
+ var slatCoordCache = this.slatCoordCache;
+ var colIndex = colCoordCache.getHorizontalIndex(leftOffset);
+ var slatIndex = slatCoordCache.getVerticalIndex(topOffset);
+
+ if (colIndex != null && slatIndex != null) {
+ var slatTop = slatCoordCache.getTopOffset(slatIndex);
+ var slatHeight = slatCoordCache.getHeight(slatIndex);
+ var partial = (topOffset - slatTop) / slatHeight; // floating point number between 0 and 1
+ var localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat
+ var snapIndex = slatIndex * snapsPerSlot + localSnapIndex;
+ var snapTop = slatTop + (localSnapIndex / snapsPerSlot) * slatHeight;
+ var snapBottom = slatTop + ((localSnapIndex + 1) / snapsPerSlot) * slatHeight;
+
+ return {
+ col: colIndex,
+ snap: snapIndex,
+ component: this, // needed unfortunately :(
+ left: colCoordCache.getLeftOffset(colIndex),
+ right: colCoordCache.getRightOffset(colIndex),
+ top: snapTop,
+ bottom: snapBottom
+ };
+ }
+ },
+
+
+ getHitSpan: function(hit) {
+ var start = this.getCellDate(0, hit.col); // row=0
+ var time = this.computeSnapTime(hit.snap); // pass in the snap-index
+ var end;
+
+ start.time(time);
+ end = start.clone().add(this.snapDuration);
+
+ return { start: start, end: end };
+ },
+
+
+ getHitEl: function(hit) {
+ return this.colEls.eq(hit.col);
+ },
+
+
+ /* Dates
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ rangeUpdated: function() {
+ this.updateDayTable();
+ },
+
+
+ // Given a row number of the grid, representing a "snap", returns a time (Duration) from its start-of-day
+ computeSnapTime: function(snapIndex) {
+ return moment.duration(this.minTime + this.snapDuration * snapIndex);
+ },
+
+
+ // Slices up the given span (unzoned start/end with other misc data) into an array of segments
+ spanToSegs: function(span) {
+ var segs = this.sliceRangeByTimes(span);
+ var i;
+
+ for (i = 0; i < segs.length; i++) {
+ if (this.isRTL) {
+ segs[i].col = this.daysPerRow - 1 - segs[i].dayIndex;
+ }
+ else {
+ segs[i].col = segs[i].dayIndex;
+ }
+ }
+
+ return segs;
+ },
+
+
+ sliceRangeByTimes: function(range) {
+ var segs = [];
+ var seg;
+ var dayIndex;
+ var dayDate;
+ var dayRange;
+
+ for (dayIndex = 0; dayIndex < this.daysPerRow; dayIndex++) {
+ dayDate = this.dayDates[dayIndex].clone(); // TODO: better API for this?
+ dayRange = {
+ start: dayDate.clone().time(this.minTime),
+ end: dayDate.clone().time(this.maxTime)
+ };
+ seg = intersectRanges(range, dayRange); // both will be ambig timezone
+ if (seg) {
+ seg.dayIndex = dayIndex;
+ segs.push(seg);
+ }
+ }
+
+ return segs;
+ },
+
+
+ /* Coordinates
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ updateSize: function(isResize) { // NOT a standard Grid method
+ this.slatCoordCache.build();
+
+ if (isResize) {
+ this.updateSegVerticals(
+ [].concat(this.fgSegs || [], this.bgSegs || [], this.businessSegs || [])
+ );
+ }
+ },
+
+
+ getTotalSlatHeight: function() {
+ return this.slatContainerEl.outerHeight();
+ },
+
+
+ // Computes the top coordinate, relative to the bounds of the grid, of the given date.
+ // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight.
+ computeDateTop: function(date, startOfDayDate) {
+ return this.computeTimeTop(
+ moment.duration(
+ date - startOfDayDate.clone().stripTime()
+ )
+ );
+ },
+
+
+ // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration).
+ computeTimeTop: function(time) {
+ var len = this.slatEls.length;
+ var slatCoverage = (time - this.minTime) / this.slotDuration; // floating-point value of # of slots covered
+ var slatIndex;
+ var slatRemainder;
+
+ // compute a floating-point number for how many slats should be progressed through.
+ // from 0 to number of slats (inclusive)
+ // constrained because minTime/maxTime might be customized.
+ slatCoverage = Math.max(0, slatCoverage);
+ slatCoverage = Math.min(len, slatCoverage);
+
+ // an integer index of the furthest whole slat
+ // from 0 to number slats (*exclusive*, so len-1)
+ slatIndex = Math.floor(slatCoverage);
+ slatIndex = Math.min(slatIndex, len - 1);
+
+ // how much further through the slatIndex slat (from 0.0-1.0) must be covered in addition.
+ // could be 1.0 if slatCoverage is covering *all* the slots
+ slatRemainder = slatCoverage - slatIndex;
+
+ return this.slatCoordCache.getTopPosition(slatIndex) +
+ this.slatCoordCache.getHeight(slatIndex) * slatRemainder;
+ },
+
+
+
+ /* Event Drag Visualization
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of an event being dragged over the specified date(s).
+ // A returned value of `true` signals that a mock "helper" event has been rendered.
+ renderDrag: function(eventLocation, seg) {
+
+ if (seg) { // if there is event information for this drag, render a helper event
+
+ // returns mock event elements
+ // signal that a helper has been rendered
+ return this.renderEventLocationHelper(eventLocation, seg);
+ }
+ else {
+ // otherwise, just render a highlight
+ this.renderHighlight(this.eventToSpan(eventLocation));
+ }
+ },
+
+
+ // Unrenders any visual indication of an event being dragged
+ unrenderDrag: function() {
+ this.unrenderHelper();
+ this.unrenderHighlight();
+ },
+
+
+ /* Event Resize Visualization
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of an event being resized
+ renderEventResize: function(eventLocation, seg) {
+ return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
+ },
+
+
+ // Unrenders any visual indication of an event being resized
+ unrenderEventResize: function() {
+ this.unrenderHelper();
+ },
+
+
+ /* Event Helper
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag)
+ renderHelper: function(event, sourceSeg) {
+ return this.renderHelperSegs(this.eventToSegs(event), sourceSeg); // returns mock event elements
+ },
+
+
+ // Unrenders any mock helper event
+ unrenderHelper: function() {
+ this.unrenderHelperSegs();
+ },
+
+
+ /* Business Hours
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderBusinessHours: function() {
+ var events = this.view.calendar.getBusinessHoursEvents();
+ var segs = this.eventsToSegs(events);
+
+ this.renderBusinessSegs(segs);
+ },
+
+
+ unrenderBusinessHours: function() {
+ this.unrenderBusinessSegs();
+ },
+
+
+ /* Now Indicator
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ getNowIndicatorUnit: function() {
+ return 'minute'; // will refresh on the minute
+ },
+
+
+ renderNowIndicator: function(date) {
+ // seg system might be overkill, but it handles scenario where line needs to be rendered
+ // more than once because of columns with the same date (resources columns for example)
+ var segs = this.spanToSegs({ start: date, end: date });
+ var top = this.computeDateTop(date, date);
+ var nodes = [];
+ var i;
+
+ // render lines within the columns
+ for (i = 0; i < segs.length; i++) {
+ nodes.push($('<div class="fc-now-indicator fc-now-indicator-line"></div>')
+ .css('top', top)
+ .appendTo(this.colContainerEls.eq(segs[i].col))[0]);
+ }
+
+ // render an arrow over the axis
+ if (segs.length > 0) { // is the current time in view?
+ nodes.push($('<div class="fc-now-indicator fc-now-indicator-arrow"></div>')
+ .css('top', top)
+ .appendTo(this.el.find('.fc-content-skeleton'))[0]);
+ }
+
+ this.nowIndicatorEls = $(nodes);
+ },
+
+
+ unrenderNowIndicator: function() {
+ if (this.nowIndicatorEls) {
+ this.nowIndicatorEls.remove();
+ this.nowIndicatorEls = null;
+ }
+ },
+
+
+ /* Selection
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight.
+ renderSelection: function(span) {
+ if (this.view.opt('selectHelper')) { // this setting signals that a mock helper event should be rendered
+
+ // normally acceps an eventLocation, span has a start/end, which is good enough
+ this.renderEventLocationHelper(span);
+ }
+ else {
+ this.renderHighlight(span);
+ }
+ },
+
+
+ // Unrenders any visual indication of a selection
+ unrenderSelection: function() {
+ this.unrenderHelper();
+ this.unrenderHighlight();
+ },
+
+
+ /* Highlight
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderHighlight: function(span) {
+ this.renderHighlightSegs(this.spanToSegs(span));
+ },
+
+
+ unrenderHighlight: function() {
+ this.unrenderHighlightSegs();
+ }
+
+});
+
+;;
+
+/* Methods for rendering SEGMENTS, pieces of content that live on the view
+ ( this file is no longer just for events )
+----------------------------------------------------------------------------------------------------------------------*/
+
+TimeGrid.mixin({
+
+ colContainerEls: null, // containers for each column
+
+ // inner-containers for each column where different types of segs live
+ fgContainerEls: null,
+ bgContainerEls: null,
+ helperContainerEls: null,
+ highlightContainerEls: null,
+ businessContainerEls: null,
+
+ // arrays of different types of displayed segments
+ fgSegs: null,
+ bgSegs: null,
+ helperSegs: null,
+ highlightSegs: null,
+ businessSegs: null,
+
+
+ // Renders the DOM that the view's content will live in
+ renderContentSkeleton: function() {
+ var cellHtml = '';
+ var i;
+ var skeletonEl;
+
+ for (i = 0; i < this.colCnt; i++) {
+ cellHtml +=
+ '<td>' +
+ '<div class="fc-content-col">' +
+ '<div class="fc-event-container fc-helper-container"></div>' +
+ '<div class="fc-event-container"></div>' +
+ '<div class="fc-highlight-container"></div>' +
+ '<div class="fc-bgevent-container"></div>' +
+ '<div class="fc-business-container"></div>' +
+ '</div>' +
+ '</td>';
+ }
+
+ skeletonEl = $(
+ '<div class="fc-content-skeleton">' +
+ '<table>' +
+ '<tr>' + cellHtml + '</tr>' +
+ '</table>' +
+ '</div>'
+ );
+
+ this.colContainerEls = skeletonEl.find('.fc-content-col');
+ this.helperContainerEls = skeletonEl.find('.fc-helper-container');
+ this.fgContainerEls = skeletonEl.find('.fc-event-container:not(.fc-helper-container)');
+ this.bgContainerEls = skeletonEl.find('.fc-bgevent-container');
+ this.highlightContainerEls = skeletonEl.find('.fc-highlight-container');
+ this.businessContainerEls = skeletonEl.find('.fc-business-container');
+
+ this.bookendCells(skeletonEl.find('tr')); // TODO: do this on string level
+ this.el.append(skeletonEl);
+ },
+
+
+ /* Foreground Events
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderFgSegs: function(segs) {
+ segs = this.renderFgSegsIntoContainers(segs, this.fgContainerEls);
+ this.fgSegs = segs;
+ return segs; // needed for Grid::renderEvents
+ },
+
+
+ unrenderFgSegs: function() {
+ this.unrenderNamedSegs('fgSegs');
+ },
+
+
+ /* Foreground Helper Events
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderHelperSegs: function(segs, sourceSeg) {
+ var helperEls = [];
+ var i, seg;
+ var sourceEl;
+
+ segs = this.renderFgSegsIntoContainers(segs, this.helperContainerEls);
+
+ // Try to make the segment that is in the same row as sourceSeg look the same
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ if (sourceSeg && sourceSeg.col === seg.col) {
+ sourceEl = sourceSeg.el;
+ seg.el.css({
+ left: sourceEl.css('left'),
+ right: sourceEl.css('right'),
+ 'margin-left': sourceEl.css('margin-left'),
+ 'margin-right': sourceEl.css('margin-right')
+ });
+ }
+ helperEls.push(seg.el[0]);
+ }
+
+ this.helperSegs = segs;
+
+ return $(helperEls); // must return rendered helpers
+ },
+
+
+ unrenderHelperSegs: function() {
+ this.unrenderNamedSegs('helperSegs');
+ },
+
+
+ /* Background Events
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderBgSegs: function(segs) {
+ segs = this.renderFillSegEls('bgEvent', segs); // TODO: old fill system
+ this.updateSegVerticals(segs);
+ this.attachSegsByCol(this.groupSegsByCol(segs), this.bgContainerEls);
+ this.bgSegs = segs;
+ return segs; // needed for Grid::renderEvents
+ },
+
+
+ unrenderBgSegs: function() {
+ this.unrenderNamedSegs('bgSegs');
+ },
+
+
+ /* Highlight
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderHighlightSegs: function(segs) {
+ segs = this.renderFillSegEls('highlight', segs); // TODO: old fill system
+ this.updateSegVerticals(segs);
+ this.attachSegsByCol(this.groupSegsByCol(segs), this.highlightContainerEls);
+ this.highlightSegs = segs;
+ },
+
+
+ unrenderHighlightSegs: function() {
+ this.unrenderNamedSegs('highlightSegs');
+ },
+
+
+ /* Business Hours
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderBusinessSegs: function(segs) {
+ segs = this.renderFillSegEls('businessHours', segs); // TODO: old fill system
+ this.updateSegVerticals(segs);
+ this.attachSegsByCol(this.groupSegsByCol(segs), this.businessContainerEls);
+ this.businessSegs = segs;
+ },
+
+
+ unrenderBusinessSegs: function() {
+ this.unrenderNamedSegs('businessSegs');
+ },
+
+
+ /* Seg Rendering Utils
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col
+ groupSegsByCol: function(segs) {
+ var segsByCol = [];
+ var i;
+
+ for (i = 0; i < this.colCnt; i++) {
+ segsByCol.push([]);
+ }
+
+ for (i = 0; i < segs.length; i++) {
+ segsByCol[segs[i].col].push(segs[i]);
+ }
+
+ return segsByCol;
+ },
+
+
+ // Given segments grouped by column, insert the segments' elements into a parallel array of container
+ // elements, each living within a column.
+ attachSegsByCol: function(segsByCol, containerEls) {
+ var col;
+ var segs;
+ var i;
+
+ for (col = 0; col < this.colCnt; col++) { // iterate each column grouping
+ segs = segsByCol[col];
+
+ for (i = 0; i < segs.length; i++) {
+ containerEls.eq(col).append(segs[i].el);
+ }
+ }
+ },
+
+
+ // Given the name of a property of `this` object, assumed to be an array of segments,
+ // loops through each segment and removes from DOM. Will null-out the property afterwards.
+ unrenderNamedSegs: function(propName) {
+ var segs = this[propName];
+ var i;
+
+ if (segs) {
+ for (i = 0; i < segs.length; i++) {
+ segs[i].el.remove();
+ }
+ this[propName] = null;
+ }
+ },
+
+
+
+ /* Foreground Event Rendering Utils
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Given an array of foreground segments, render a DOM element for each, computes position,
+ // and attaches to the column inner-container elements.
+ renderFgSegsIntoContainers: function(segs, containerEls) {
+ var segsByCol;
+ var col;
+
+ segs = this.renderFgSegEls(segs); // will call fgSegHtml
+ segsByCol = this.groupSegsByCol(segs);
+
+ for (col = 0; col < this.colCnt; col++) {
+ this.updateFgSegCoords(segsByCol[col]);
+ }
+
+ this.attachSegsByCol(segsByCol, containerEls);
+
+ return segs;
+ },
+
+
+ // Renders the HTML for a single event segment's default rendering
+ fgSegHtml: function(seg, disableResizing) {
+ var view = this.view;
+ var event = seg.event;
+ var isDraggable = view.isEventDraggable(event);
+ var isResizableFromStart = !disableResizing && seg.isStart && view.isEventResizableFromStart(event);
+ var isResizableFromEnd = !disableResizing && seg.isEnd && view.isEventResizableFromEnd(event);
+ var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd);
+ var skinCss = cssToStr(this.getSegSkinCss(seg));
+ var timeText;
+ var fullTimeText; // more verbose time text. for the print stylesheet
+ var startTimeText; // just the start time text
+
+ classes.unshift('fc-time-grid-event', 'fc-v-event');
+
+ if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day...
+ // Don't display time text on segments that run entirely through a day.
+ // That would appear as midnight-midnight and would look dumb.
+ // Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am)
+ if (seg.isStart || seg.isEnd) {
+ timeText = this.getEventTimeText(seg);
+ fullTimeText = this.getEventTimeText(seg, 'LT');
+ startTimeText = this.getEventTimeText(seg, null, false); // displayEnd=false
+ }
+ } else {
+ // Display the normal time text for the *event's* times
+ timeText = this.getEventTimeText(event);
+ fullTimeText = this.getEventTimeText(event, 'LT');
+ startTimeText = this.getEventTimeText(event, null, false); // displayEnd=false
+ }
+
+ return '<a class="' + classes.join(' ') + '"' +
+ (event.url ?
+ ' href="' + htmlEscape(event.url) + '"' :
+ ''
+ ) +
+ (skinCss ?
+ ' style="' + skinCss + '"' :
+ ''
+ ) +
+ '>' +
+ '<div class="fc-content">' +
+ (timeText ?
+ '<div class="fc-time"' +
+ ' data-start="' + htmlEscape(startTimeText) + '"' +
+ ' data-full="' + htmlEscape(fullTimeText) + '"' +
+ '>' +
+ '<span>' + htmlEscape(timeText) + '</span>' +
+ '</div>' :
+ ''
+ ) +
+ (event.title ?
+ '<div class="fc-title">' +
+ htmlEscape(event.title) +
+ '</div>' :
+ ''
+ ) +
+ '</div>' +
+ '<div class="fc-bg"/>' +
+ /* TODO: write CSS for this
+ (isResizableFromStart ?
+ '<div class="fc-resizer fc-start-resizer" />' :
+ ''
+ ) +
+ */
+ (isResizableFromEnd ?
+ '<div class="fc-resizer fc-end-resizer" />' :
+ ''
+ ) +
+ '</a>';
+ },
+
+
+ /* Seg Position Utils
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Refreshes the CSS top/bottom coordinates for each segment element.
+ // Works when called after initial render, after a window resize/zoom for example.
+ updateSegVerticals: function(segs) {
+ this.computeSegVerticals(segs);
+ this.assignSegVerticals(segs);
+ },
+
+
+ // For each segment in an array, computes and assigns its top and bottom properties
+ computeSegVerticals: function(segs) {
+ var i, seg;
+
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ seg.top = this.computeDateTop(seg.start, seg.start);
+ seg.bottom = this.computeDateTop(seg.end, seg.start);
+ }
+ },
+
+
+ // Given segments that already have their top/bottom properties computed, applies those values to
+ // the segments' elements.
+ assignSegVerticals: function(segs) {
+ var i, seg;
+
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ seg.el.css(this.generateSegVerticalCss(seg));
+ }
+ },
+
+
+ // Generates an object with CSS properties for the top/bottom coordinates of a segment element
+ generateSegVerticalCss: function(seg) {
+ return {
+ top: seg.top,
+ bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container
+ };
+ },
+
+
+ /* Foreground Event Positioning Utils
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Given segments that are assumed to all live in the *same column*,
+ // compute their verical/horizontal coordinates and assign to their elements.
+ updateFgSegCoords: function(segs) {
+ this.computeSegVerticals(segs); // horizontals relies on this
+ this.computeFgSegHorizontals(segs); // compute horizontal coordinates, z-index's, and reorder the array
+ this.assignSegVerticals(segs);
+ this.assignFgSegHorizontals(segs);
+ },
+
+
+ // Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each.
+ // NOTE: Also reorders the given array by date!
+ computeFgSegHorizontals: function(segs) {
+ var levels;
+ var level0;
+ var i;
+
+ this.sortEventSegs(segs); // order by certain criteria
+ levels = buildSlotSegLevels(segs);
+ computeForwardSlotSegs(levels);
+
+ if ((level0 = levels[0])) {
+
+ for (i = 0; i < level0.length; i++) {
+ computeSlotSegPressures(level0[i]);
+ }
+
+ for (i = 0; i < level0.length; i++) {
+ this.computeFgSegForwardBack(level0[i], 0, 0);
+ }
+ }
+ },
+
+
+ // Calculate seg.forwardCoord and seg.backwardCoord for the segment, where both values range
+ // from 0 to 1. If the calendar is left-to-right, the seg.backwardCoord maps to "left" and
+ // seg.forwardCoord maps to "right" (via percentage). Vice-versa if the calendar is right-to-left.
+ //
+ // The segment might be part of a "series", which means consecutive segments with the same pressure
+ // who's width is unknown until an edge has been hit. `seriesBackwardPressure` is the number of
+ // segments behind this one in the current series, and `seriesBackwardCoord` is the starting
+ // coordinate of the first segment in the series.
+ computeFgSegForwardBack: function(seg, seriesBackwardPressure, seriesBackwardCoord) {
+ var forwardSegs = seg.forwardSegs;
+ var i;
+
+ if (seg.forwardCoord === undefined) { // not already computed
+
+ if (!forwardSegs.length) {
+
+ // if there are no forward segments, this segment should butt up against the edge
+ seg.forwardCoord = 1;
+ }
+ else {
+
+ // sort highest pressure first
+ this.sortForwardSegs(forwardSegs);
+
+ // this segment's forwardCoord will be calculated from the backwardCoord of the
+ // highest-pressure forward segment.
+ this.computeFgSegForwardBack(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord);
+ seg.forwardCoord = forwardSegs[0].backwardCoord;
+ }
+
+ // calculate the backwardCoord from the forwardCoord. consider the series
+ seg.backwardCoord = seg.forwardCoord -
+ (seg.forwardCoord - seriesBackwardCoord) / // available width for series
+ (seriesBackwardPressure + 1); // # of segments in the series
+
+ // use this segment's coordinates to computed the coordinates of the less-pressurized
+ // forward segments
+ for (i=0; i<forwardSegs.length; i++) {
+ this.computeFgSegForwardBack(forwardSegs[i], 0, seg.forwardCoord);
+ }
+ }
+ },
+
+
+ sortForwardSegs: function(forwardSegs) {
+ forwardSegs.sort(proxy(this, 'compareForwardSegs'));
+ },
+
+
+ // A cmp function for determining which forward segment to rely on more when computing coordinates.
+ compareForwardSegs: function(seg1, seg2) {
+ // put higher-pressure first
+ return seg2.forwardPressure - seg1.forwardPressure ||
+ // put segments that are closer to initial edge first (and favor ones with no coords yet)
+ (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) ||
+ // do normal sorting...
+ this.compareEventSegs(seg1, seg2);
+ },
+
+
+ // Given foreground event segments that have already had their position coordinates computed,
+ // assigns position-related CSS values to their elements.
+ assignFgSegHorizontals: function(segs) {
+ var i, seg;
+
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ seg.el.css(this.generateFgSegHorizontalCss(seg));
+
+ // if the height is short, add a className for alternate styling
+ if (seg.bottom - seg.top < 30) {
+ seg.el.addClass('fc-short');
+ }
+ }
+ },
+
+
+ // Generates an object with CSS properties/values that should be applied to an event segment element.
+ // Contains important positioning-related properties that should be applied to any event element, customized or not.
+ generateFgSegHorizontalCss: function(seg) {
+ var shouldOverlap = this.view.opt('slotEventOverlap');
+ var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point
+ var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point
+ var props = this.generateSegVerticalCss(seg); // get top/bottom first
+ var left; // amount of space from left edge, a fraction of the total width
+ var right; // amount of space from right edge, a fraction of the total width
+
+ if (shouldOverlap) {
+ // double the width, but don't go beyond the maximum forward coordinate (1.0)
+ forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2);
+ }
+
+ if (this.isRTL) {
+ left = 1 - forwardCoord;
+ right = backwardCoord;
+ }
+ else {
+ left = backwardCoord;
+ right = 1 - forwardCoord;
+ }
+
+ props.zIndex = seg.level + 1; // convert from 0-base to 1-based
+ props.left = left * 100 + '%';
+ props.right = right * 100 + '%';
+
+ if (shouldOverlap && seg.forwardPressure) {
+ // add padding to the edge so that forward stacked events don't cover the resizer's icon
+ props[this.isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width
+ }
+
+ return props;
+ }
+
+});
+
+
+// Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is
+// left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date.
+function buildSlotSegLevels(segs) {
+ var levels = [];
+ var i, seg;
+ var j;
+
+ for (i=0; i<segs.length; i++) {
+ seg = segs[i];
+
+ // go through all the levels and stop on the first level where there are no collisions
+ for (j=0; j<levels.length; j++) {
+ if (!computeSlotSegCollisions(seg, levels[j]).length) {
+ break;
+ }
+ }
+
+ seg.level = j;
+
+ (levels[j] || (levels[j] = [])).push(seg);
+ }
+
+ return levels;
+}
+
+
+// For every segment, figure out the other segments that are in subsequent
+// levels that also occupy the same vertical space. Accumulate in seg.forwardSegs
+function computeForwardSlotSegs(levels) {
+ var i, level;
+ var j, seg;
+ var k;
+
+ for (i=0; i<levels.length; i++) {
+ level = levels[i];
+
+ for (j=0; j<level.length; j++) {
+ seg = level[j];
+
+ seg.forwardSegs = [];
+ for (k=i+1; k<levels.length; k++) {
+ computeSlotSegCollisions(seg, levels[k], seg.forwardSegs);
+ }
+ }
+ }
+}
+
+
+// Figure out which path forward (via seg.forwardSegs) results in the longest path until
+// the furthest edge is reached. The number of segments in this path will be seg.forwardPressure
+function computeSlotSegPressures(seg) {
+ var forwardSegs = seg.forwardSegs;
+ var forwardPressure = 0;
+ var i, forwardSeg;
+
+ if (seg.forwardPressure === undefined) { // not already computed
+
+ for (i=0; i<forwardSegs.length; i++) {
+ forwardSeg = forwardSegs[i];
+
+ // figure out the child's maximum forward path
+ computeSlotSegPressures(forwardSeg);
+
+ // either use the existing maximum, or use the child's forward pressure
+ // plus one (for the forwardSeg itself)
+ forwardPressure = Math.max(
+ forwardPressure,
+ 1 + forwardSeg.forwardPressure
+ );
+ }
+
+ seg.forwardPressure = forwardPressure;
+ }
+}
+
+
+// Find all the segments in `otherSegs` that vertically collide with `seg`.
+// Append into an optionally-supplied `results` array and return.
+function computeSlotSegCollisions(seg, otherSegs, results) {
+ results = results || [];
+
+ for (var i=0; i<otherSegs.length; i++) {
+ if (isSlotSegCollision(seg, otherSegs[i])) {
+ results.push(otherSegs[i]);
+ }
+ }
+
+ return results;
+}
+
+
+// Do these segments occupy the same vertical space?
+function isSlotSegCollision(seg1, seg2) {
+ return seg1.bottom > seg2.top && seg1.top < seg2.bottom;
+}
+
+;;
+
+/* An abstract class from which other views inherit from
+----------------------------------------------------------------------------------------------------------------------*/
+
+var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
+
+ type: null, // subclass' view name (string)
+ name: null, // deprecated. use `type` instead
+ title: null, // the text that will be displayed in the header's title
+
+ calendar: null, // owner Calendar object
+ options: null, // hash containing all options. already merged with view-specific-options
+ el: null, // the view's containing element. set by Calendar
+
+ displaying: null, // a promise representing the state of rendering. null if no render requested
+ isSkeletonRendered: false,
+ isEventsRendered: false,
+
+ // range the view is actually displaying (moments)
+ start: null,
+ end: null, // exclusive
+
+ // range the view is formally responsible for (moments)
+ // may be different from start/end. for example, a month view might have 1st-31st, excluding padded dates
+ intervalStart: null,
+ intervalEnd: null, // exclusive
+ intervalDuration: null,
+ intervalUnit: null, // name of largest unit being displayed, like "month" or "week"
+
+ isRTL: false,
+ isSelected: false, // boolean whether a range of time is user-selected or not
+ selectedEvent: null,
+
+ eventOrderSpecs: null, // criteria for ordering events when they have same date/time
+
+ // classNames styled by jqui themes
+ widgetHeaderClass: null,
+ widgetContentClass: null,
+ highlightStateClass: null,
+
+ // for date utils, computed from options
+ nextDayThreshold: null,
+ isHiddenDayHash: null,
+
+ // now indicator
+ isNowIndicatorRendered: null,
+ initialNowDate: null, // result first getNow call
+ initialNowQueriedMs: null, // ms time the getNow was called
+ nowIndicatorTimeoutID: null, // for refresh timing of now indicator
+ nowIndicatorIntervalID: null, // "
+
+
+ constructor: function(calendar, type, options, intervalDuration) {
+
+ this.calendar = calendar;
+ this.type = this.name = type; // .name is deprecated
+ this.options = options;
+ this.intervalDuration = intervalDuration || moment.duration(1, 'day');
+
+ this.nextDayThreshold = moment.duration(this.opt('nextDayThreshold'));
+ this.initThemingProps();
+ this.initHiddenDays();
+ this.isRTL = this.opt('isRTL');
+
+ this.eventOrderSpecs = parseFieldSpecs(this.opt('eventOrder'));
+
+ this.initialize();
+ },
+
+
+ // A good place for subclasses to initialize member variables
+ initialize: function() {
+ // subclasses can implement
+ },
+
+
+ // Retrieves an option with the given name
+ opt: function(name) {
+ return this.options[name];
+ },
+
+
+ // Triggers handlers that are view-related. Modifies args before passing to calendar.
+ trigger: function(name, thisObj) { // arguments beyond thisObj are passed along
+ var calendar = this.calendar;
+
+ return calendar.trigger.apply(
+ calendar,
+ [name, thisObj || this].concat(
+ Array.prototype.slice.call(arguments, 2), // arguments beyond thisObj
+ [ this ] // always make the last argument a reference to the view. TODO: deprecate
+ )
+ );
+ },
+
+
+ /* Dates
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Updates all internal dates to center around the given current unzoned date.
+ setDate: function(date) {
+ this.setRange(this.computeRange(date));
+ },
+
+
+ // Updates all internal dates for displaying the given unzoned range.
+ setRange: function(range) {
+ $.extend(this, range); // assigns every property to this object's member variables
+ this.updateTitle();
+ },
+
+
+ // Given a single current unzoned date, produce information about what range to display.
+ // Subclasses can override. Must return all properties.
+ computeRange: function(date) {
+ var intervalUnit = computeIntervalUnit(this.intervalDuration);
+ var intervalStart = date.clone().startOf(intervalUnit);
+ var intervalEnd = intervalStart.clone().add(this.intervalDuration);
+ var start, end;
+
+ // normalize the range's time-ambiguity
+ if (/year|month|week|day/.test(intervalUnit)) { // whole-days?
+ intervalStart.stripTime();
+ intervalEnd.stripTime();
+ }
+ else { // needs to have a time?
+ if (!intervalStart.hasTime()) {
+ intervalStart = this.calendar.time(0); // give 00:00 time
+ }
+ if (!intervalEnd.hasTime()) {
+ intervalEnd = this.calendar.time(0); // give 00:00 time
+ }
+ }
+
+ start = intervalStart.clone();
+ start = this.skipHiddenDays(start);
+ end = intervalEnd.clone();
+ end = this.skipHiddenDays(end, -1, true); // exclusively move backwards
+
+ return {
+ intervalUnit: intervalUnit,
+ intervalStart: intervalStart,
+ intervalEnd: intervalEnd,
+ start: start,
+ end: end
+ };
+ },
+
+
+ // Computes the new date when the user hits the prev button, given the current date
+ computePrevDate: function(date) {
+ return this.massageCurrentDate(
+ date.clone().startOf(this.intervalUnit).subtract(this.intervalDuration), -1
+ );
+ },
+
+
+ // Computes the new date when the user hits the next button, given the current date
+ computeNextDate: function(date) {
+ return this.massageCurrentDate(
+ date.clone().startOf(this.intervalUnit).add(this.intervalDuration)
+ );
+ },
+
+
+ // Given an arbitrarily calculated current date of the calendar, returns a date that is ensured to be completely
+ // visible. `direction` is optional and indicates which direction the current date was being
+ // incremented or decremented (1 or -1).
+ massageCurrentDate: function(date, direction) {
+ if (this.intervalDuration.as('days') <= 1) { // if the view displays a single day or smaller
+ if (this.isHiddenDay(date)) {
+ date = this.skipHiddenDays(date, direction);
+ date.startOf('day');
+ }
+ }
+
+ return date;
+ },
+
+
+ /* Title and Date Formatting
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Sets the view's title property to the most updated computed value
+ updateTitle: function() {
+ this.title = this.computeTitle();
+ },
+
+
+ // Computes what the title at the top of the calendar should be for this view
+ computeTitle: function() {
+ return this.formatRange(
+ {
+ // in case intervalStart/End has a time, make sure timezone is correct
+ start: this.calendar.applyTimezone(this.intervalStart),
+ end: this.calendar.applyTimezone(this.intervalEnd)
+ },
+ this.opt('titleFormat') || this.computeTitleFormat(),
+ this.opt('titleRangeSeparator')
+ );
+ },
+
+
+ // Generates the format string that should be used to generate the title for the current date range.
+ // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`.
+ computeTitleFormat: function() {
+ if (this.intervalUnit == 'year') {
+ return 'YYYY';
+ }
+ else if (this.intervalUnit == 'month') {
+ return this.opt('monthYearFormat'); // like "September 2014"
+ }
+ else if (this.intervalDuration.as('days') > 1) {
+ return 'll'; // multi-day range. shorter, like "Sep 9 - 10 2014"
+ }
+ else {
+ return 'LL'; // one day. longer, like "September 9 2014"
+ }
+ },
+
+
+ // Utility for formatting a range. Accepts a range object, formatting string, and optional separator.
+ // Displays all-day ranges naturally, with an inclusive end. Takes the current isRTL into account.
+ // The timezones of the dates within `range` will be respected.
+ formatRange: function(range, formatStr, separator) {
+ var end = range.end;
+
+ if (!end.hasTime()) { // all-day?
+ end = end.clone().subtract(1); // convert to inclusive. last ms of previous day
+ }
+
+ return formatRange(range.start, end, formatStr, separator, this.opt('isRTL'));
+ },
+
+
+ /* Rendering
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Sets the container element that the view should render inside of.
+ // Does other DOM-related initializations.
+ setElement: function(el) {
+ this.el = el;
+ this.bindGlobalHandlers();
+ },
+
+
+ // Removes the view's container element from the DOM, clearing any content beforehand.
+ // Undoes any other DOM-related attachments.
+ removeElement: function() {
+ this.clear(); // clears all content
+
+ // clean up the skeleton
+ if (this.isSkeletonRendered) {
+ this.unrenderSkeleton();
+ this.isSkeletonRendered = false;
+ }
+
+ this.unbindGlobalHandlers();
+
+ this.el.remove();
+
+ // NOTE: don't null-out this.el in case the View was destroyed within an API callback.
+ // We don't null-out the View's other jQuery element references upon destroy,
+ // so we shouldn't kill this.el either.
+ },
+
+
+ // Does everything necessary to display the view centered around the given unzoned date.
+ // Does every type of rendering EXCEPT rendering events.
+ // Is asychronous and returns a promise.
+ display: function(date) {
+ var _this = this;
+ var scrollState = null;
+
+ if (this.displaying) {
+ scrollState = this.queryScroll();
+ }
+
+ this.calendar.freezeContentHeight();
+
+ return this.clear().then(function() { // clear the content first (async)
+ return (
+ _this.displaying =
+ $.when(_this.displayView(date)) // displayView might return a promise
+ .then(function() {
+ _this.forceScroll(_this.computeInitialScroll(scrollState));
+ _this.calendar.unfreezeContentHeight();
+ _this.triggerRender();
+ })
+ );
+ });
+ },
+
+
+ // Does everything necessary to clear the content of the view.
+ // Clears dates and events. Does not clear the skeleton.
+ // Is asychronous and returns a promise.
+ clear: function() {
+ var _this = this;
+ var displaying = this.displaying;
+
+ if (displaying) { // previously displayed, or in the process of being displayed?
+ return displaying.then(function() { // wait for the display to finish
+ _this.displaying = null;
+ _this.clearEvents();
+ return _this.clearView(); // might return a promise. chain it
+ });
+ }
+ else {
+ return $.when(); // an immediately-resolved promise
+ }
+ },
+
+
+ // Displays the view's non-event content, such as date-related content or anything required by events.
+ // Renders the view's non-content skeleton if necessary.
+ // Can be asynchronous and return a promise.
+ displayView: function(date) {
+ if (!this.isSkeletonRendered) {
+ this.renderSkeleton();
+ this.isSkeletonRendered = true;
+ }
+ if (date) {
+ this.setDate(date);
+ }
+ if (this.render) {
+ this.render(); // TODO: deprecate
+ }
+ this.renderDates();
+ this.updateSize();
+ this.renderBusinessHours(); // might need coordinates, so should go after updateSize()
+ this.startNowIndicator();
+ },
+
+
+ // Unrenders the view content that was rendered in displayView.
+ // Can be asynchronous and return a promise.
+ clearView: function() {
+ this.unselect();
+ this.stopNowIndicator();
+ this.triggerUnrender();
+ this.unrenderBusinessHours();
+ this.unrenderDates();
+ if (this.destroy) {
+ this.destroy(); // TODO: deprecate
+ }
+ },
+
+
+ // Renders the basic structure of the view before any content is rendered
+ renderSkeleton: function() {
+ // subclasses should implement
+ },
+
+
+ // Unrenders the basic structure of the view
+ unrenderSkeleton: function() {
+ // subclasses should implement
+ },
+
+
+ // Renders the view's date-related content.
+ // Assumes setRange has already been called and the skeleton has already been rendered.
+ renderDates: function() {
+ // subclasses should implement
+ },
+
+
+ // Unrenders the view's date-related content
+ unrenderDates: function() {
+ // subclasses should override
+ },
+
+
+ // Signals that the view's content has been rendered
+ triggerRender: function() {
+ this.trigger('viewRender', this, this, this.el);
+ },
+
+
+ // Signals that the view's content is about to be unrendered
+ triggerUnrender: function() {
+ this.trigger('viewDestroy', this, this, this.el);
+ },
+
+
+ // Binds DOM handlers to elements that reside outside the view container, such as the document
+ bindGlobalHandlers: function() {
+ this.listenTo($(document), 'mousedown', this.handleDocumentMousedown);
+ this.listenTo($(document), 'touchstart', this.processUnselect);
+ },
+
+
+ // Unbinds DOM handlers from elements that reside outside the view container
+ unbindGlobalHandlers: function() {
+ this.stopListeningTo($(document));
+ },
+
+
+ // Initializes internal variables related to theming
+ initThemingProps: function() {
+ var tm = this.opt('theme') ? 'ui' : 'fc';
+
+ this.widgetHeaderClass = tm + '-widget-header';
+ this.widgetContentClass = tm + '-widget-content';
+ this.highlightStateClass = tm + '-state-highlight';
+ },
+
+
+ /* Business Hours
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders business-hours onto the view. Assumes updateSize has already been called.
+ renderBusinessHours: function() {
+ // subclasses should implement
+ },
+
+
+ // Unrenders previously-rendered business-hours
+ unrenderBusinessHours: function() {
+ // subclasses should implement
+ },
+
+
+ /* Now Indicator
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Immediately render the current time indicator and begins re-rendering it at an interval,
+ // which is defined by this.getNowIndicatorUnit().
+ // TODO: somehow do this for the current whole day's background too
+ startNowIndicator: function() {
+ var _this = this;
+ var unit;
+ var update;
+ var delay; // ms wait value
+
+ if (this.opt('nowIndicator')) {
+ unit = this.getNowIndicatorUnit();
+ if (unit) {
+ update = proxy(this, 'updateNowIndicator'); // bind to `this`
+
+ this.initialNowDate = this.calendar.getNow();
+ this.initialNowQueriedMs = +new Date();
+ this.renderNowIndicator(this.initialNowDate);
+ this.isNowIndicatorRendered = true;
+
+ // wait until the beginning of the next interval
+ delay = this.initialNowDate.clone().startOf(unit).add(1, unit) - this.initialNowDate;
+ this.nowIndicatorTimeoutID = setTimeout(function() {
+ _this.nowIndicatorTimeoutID = null;
+ update();
+ delay = +moment.duration(1, unit);
+ delay = Math.max(100, delay); // prevent too frequent
+ _this.nowIndicatorIntervalID = setInterval(update, delay); // update every interval
+ }, delay);
+ }
+ }
+ },
+
+
+ // rerenders the now indicator, computing the new current time from the amount of time that has passed
+ // since the initial getNow call.
+ updateNowIndicator: function() {
+ if (this.isNowIndicatorRendered) {
+ this.unrenderNowIndicator();
+ this.renderNowIndicator(
+ this.initialNowDate.clone().add(new Date() - this.initialNowQueriedMs) // add ms
+ );
+ }
+ },
+
+
+ // Immediately unrenders the view's current time indicator and stops any re-rendering timers.
+ // Won't cause side effects if indicator isn't rendered.
+ stopNowIndicator: function() {
+ if (this.isNowIndicatorRendered) {
+
+ if (this.nowIndicatorTimeoutID) {
+ clearTimeout(this.nowIndicatorTimeoutID);
+ this.nowIndicatorTimeoutID = null;
+ }
+ if (this.nowIndicatorIntervalID) {
+ clearTimeout(this.nowIndicatorIntervalID);
+ this.nowIndicatorIntervalID = null;
+ }
+
+ this.unrenderNowIndicator();
+ this.isNowIndicatorRendered = false;
+ }
+ },
+
+
+ // Returns a string unit, like 'second' or 'minute' that defined how often the current time indicator
+ // should be refreshed. If something falsy is returned, no time indicator is rendered at all.
+ getNowIndicatorUnit: function() {
+ // subclasses should implement
+ },
+
+
+ // Renders a current time indicator at the given datetime
+ renderNowIndicator: function(date) {
+ // subclasses should implement
+ },
+
+
+ // Undoes the rendering actions from renderNowIndicator
+ unrenderNowIndicator: function() {
+ // subclasses should implement
+ },
+
+
+ /* Dimensions
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Refreshes anything dependant upon sizing of the container element of the grid
+ updateSize: function(isResize) {
+ var scrollState;
+
+ if (isResize) {
+ scrollState = this.queryScroll();
+ }
+
+ this.updateHeight(isResize);
+ this.updateWidth(isResize);
+ this.updateNowIndicator();
+
+ if (isResize) {
+ this.setScroll(scrollState);
+ }
+ },
+
+
+ // Refreshes the horizontal dimensions of the calendar
+ updateWidth: function(isResize) {
+ // subclasses should implement
+ },
+
+
+ // Refreshes the vertical dimensions of the calendar
+ updateHeight: function(isResize) {
+ var calendar = this.calendar; // we poll the calendar for height information
+
+ this.setHeight(
+ calendar.getSuggestedViewHeight(),
+ calendar.isHeightAuto()
+ );
+ },
+
+
+ // Updates the vertical dimensions of the calendar to the specified height.
+ // if `isAuto` is set to true, height becomes merely a suggestion and the view should use its "natural" height.
+ setHeight: function(height, isAuto) {
+ // subclasses should implement
+ },
+
+
+ /* Scroller
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Computes the initial pre-configured scroll state prior to allowing the user to change it.
+ // Given the scroll state from the previous rendering. If first time rendering, given null.
+ computeInitialScroll: function(previousScrollState) {
+ return 0;
+ },
+
+
+ // Retrieves the view's current natural scroll state. Can return an arbitrary format.
+ queryScroll: function() {
+ // subclasses must implement
+ },
+
+
+ // Sets the view's scroll state. Will accept the same format computeInitialScroll and queryScroll produce.
+ setScroll: function(scrollState) {
+ // subclasses must implement
+ },
+
+
+ // Sets the scroll state, making sure to overcome any predefined scroll value the browser has in mind
+ forceScroll: function(scrollState) {
+ var _this = this;
+
+ this.setScroll(scrollState);
+ setTimeout(function() {
+ _this.setScroll(scrollState);
+ }, 0);
+ },
+
+
+ /* Event Elements / Segments
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Does everything necessary to display the given events onto the current view
+ displayEvents: function(events) {
+ var scrollState = this.queryScroll();
+
+ this.clearEvents();
+ this.renderEvents(events);
+ this.isEventsRendered = true;
+ this.setScroll(scrollState);
+ this.triggerEventRender();
+ },
+
+
+ // Does everything necessary to clear the view's currently-rendered events
+ clearEvents: function() {
+ var scrollState;
+
+ if (this.isEventsRendered) {
+
+ // TODO: optimize: if we know this is part of a displayEvents call, don't queryScroll/setScroll
+ scrollState = this.queryScroll();
+
+ this.triggerEventUnrender();
+ if (this.destroyEvents) {
+ this.destroyEvents(); // TODO: deprecate
+ }
+ this.unrenderEvents();
+ this.setScroll(scrollState);
+ this.isEventsRendered = false;
+ }
+ },
+
+
+ // Renders the events onto the view.
+ renderEvents: function(events) {
+ // subclasses should implement
+ },
+
+
+ // Removes event elements from the view.
+ unrenderEvents: function() {
+ // subclasses should implement
+ },
+
+
+ // Signals that all events have been rendered
+ triggerEventRender: function() {
+ this.renderedEventSegEach(function(seg) {
+ this.trigger('eventAfterRender', seg.event, seg.event, seg.el);
+ });
+ this.trigger('eventAfterAllRender');
+ },
+
+
+ // Signals that all event elements are about to be removed
+ triggerEventUnrender: function() {
+ this.renderedEventSegEach(function(seg) {
+ this.trigger('eventDestroy', seg.event, seg.event, seg.el);
+ });
+ },
+
+
+ // Given an event and the default element used for rendering, returns the element that should actually be used.
+ // Basically runs events and elements through the eventRender hook.
+ resolveEventEl: function(event, el) {
+ var custom = this.trigger('eventRender', event, event, el);
+
+ if (custom === false) { // means don't render at all
+ el = null;
+ }
+ else if (custom && custom !== true) {
+ el = $(custom);
+ }
+
+ return el;
+ },
+
+
+ // Hides all rendered event segments linked to the given event
+ showEvent: function(event) {
+ this.renderedEventSegEach(function(seg) {
+ seg.el.css('visibility', '');
+ }, event);
+ },
+
+
+ // Shows all rendered event segments linked to the given event
+ hideEvent: function(event) {
+ this.renderedEventSegEach(function(seg) {
+ seg.el.css('visibility', 'hidden');
+ }, event);
+ },
+
+
+ // Iterates through event segments that have been rendered (have an el). Goes through all by default.
+ // If the optional `event` argument is specified, only iterates through segments linked to that event.
+ // The `this` value of the callback function will be the view.
+ renderedEventSegEach: function(func, event) {
+ var segs = this.getEventSegs();
+ var i;
+
+ for (i = 0; i < segs.length; i++) {
+ if (!event || segs[i].event._id === event._id) {
+ if (segs[i].el) {
+ func.call(this, segs[i]);
+ }
+ }
+ }
+ },
+
+
+ // Retrieves all the rendered segment objects for the view
+ getEventSegs: function() {
+ // subclasses must implement
+ return [];
+ },
+
+
+ /* Event Drag-n-Drop
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Computes if the given event is allowed to be dragged by the user
+ isEventDraggable: function(event) {
+ var source = event.source || {};
+
+ return firstDefined(
+ event.startEditable,
+ source.startEditable,
+ this.opt('eventStartEditable'),
+ event.editable,
+ source.editable,
+ this.opt('editable')
+ );
+ },
+
+
+ // Must be called when an event in the view is dropped onto new location.
+ // `dropLocation` is an object that contains the new zoned start/end/allDay values for the event.
+ reportEventDrop: function(event, dropLocation, largeUnit, el, ev) {
+ var calendar = this.calendar;
+ var mutateResult = calendar.mutateEvent(event, dropLocation, largeUnit);
+ var undoFunc = function() {
+ mutateResult.undo();
+ calendar.reportEventChange();
+ };
+
+ this.triggerEventDrop(event, mutateResult.dateDelta, undoFunc, el, ev);
+ calendar.reportEventChange(); // will rerender events
+ },
+
+
+ // Triggers event-drop handlers that have subscribed via the API
+ triggerEventDrop: function(event, dateDelta, undoFunc, el, ev) {
+ this.trigger('eventDrop', el[0], event, dateDelta, undoFunc, ev, {}); // {} = jqui dummy
+ },
+
+
+ /* External Element Drag-n-Drop
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Must be called when an external element, via jQuery UI, has been dropped onto the calendar.
+ // `meta` is the parsed data that has been embedded into the dragging event.
+ // `dropLocation` is an object that contains the new zoned start/end/allDay values for the event.
+ reportExternalDrop: function(meta, dropLocation, el, ev, ui) {
+ var eventProps = meta.eventProps;
+ var eventInput;
+ var event;
+
+ // Try to build an event object and render it. TODO: decouple the two
+ if (eventProps) {
+ eventInput = $.extend({}, eventProps, dropLocation);
+ event = this.calendar.renderEvent(eventInput, meta.stick)[0]; // renderEvent returns an array
+ }
+
+ this.triggerExternalDrop(event, dropLocation, el, ev, ui);
+ },
+
+
+ // Triggers external-drop handlers that have subscribed via the API
+ triggerExternalDrop: function(event, dropLocation, el, ev, ui) {
+
+ // trigger 'drop' regardless of whether element represents an event
+ this.trigger('drop', el[0], dropLocation.start, ev, ui);
+
+ if (event) {
+ this.trigger('eventReceive', null, event); // signal an external event landed
+ }
+ },
+
+
+ /* Drag-n-Drop Rendering (for both events and external elements)
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of a event or external-element drag over the given drop zone.
+ // If an external-element, seg will be `null`.
+ // Must return elements used for any mock events.
+ renderDrag: function(dropLocation, seg) {
+ // subclasses must implement
+ },
+
+
+ // Unrenders a visual indication of an event or external-element being dragged.
+ unrenderDrag: function() {
+ // subclasses must implement
+ },
+
+
+ /* Event Resizing
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Computes if the given event is allowed to be resized from its starting edge
+ isEventResizableFromStart: function(event) {
+ return this.opt('eventResizableFromStart') && this.isEventResizable(event);
+ },
+
+
+ // Computes if the given event is allowed to be resized from its ending edge
+ isEventResizableFromEnd: function(event) {
+ return this.isEventResizable(event);
+ },
+
+
+ // Computes if the given event is allowed to be resized by the user at all
+ isEventResizable: function(event) {
+ var source = event.source || {};
+
+ return firstDefined(
+ event.durationEditable,
+ source.durationEditable,
+ this.opt('eventDurationEditable'),
+ event.editable,
+ source.editable,
+ this.opt('editable')
+ );
+ },
+
+
+ // Must be called when an event in the view has been resized to a new length
+ reportEventResize: function(event, resizeLocation, largeUnit, el, ev) {
+ var calendar = this.calendar;
+ var mutateResult = calendar.mutateEvent(event, resizeLocation, largeUnit);
+ var undoFunc = function() {
+ mutateResult.undo();
+ calendar.reportEventChange();
+ };
+
+ this.triggerEventResize(event, mutateResult.durationDelta, undoFunc, el, ev);
+ calendar.reportEventChange(); // will rerender events
+ },
+
+
+ // Triggers event-resize handlers that have subscribed via the API
+ triggerEventResize: function(event, durationDelta, undoFunc, el, ev) {
+ this.trigger('eventResize', el[0], event, durationDelta, undoFunc, ev, {}); // {} = jqui dummy
+ },
+
+
+ /* Selection (time range)
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Selects a date span on the view. `start` and `end` are both Moments.
+ // `ev` is the native mouse event that begin the interaction.
+ select: function(span, ev) {
+ this.unselect(ev);
+ this.renderSelection(span);
+ this.reportSelection(span, ev);
+ },
+
+
+ // Renders a visual indication of the selection
+ renderSelection: function(span) {
+ // subclasses should implement
+ },
+
+
+ // Called when a new selection is made. Updates internal state and triggers handlers.
+ reportSelection: function(span, ev) {
+ this.isSelected = true;
+ this.triggerSelect(span, ev);
+ },
+
+
+ // Triggers handlers to 'select'
+ triggerSelect: function(span, ev) {
+ this.trigger(
+ 'select',
+ null,
+ this.calendar.applyTimezone(span.start), // convert to calendar's tz for external API
+ this.calendar.applyTimezone(span.end), // "
+ ev
+ );
+ },
+
+
+ // Undoes a selection. updates in the internal state and triggers handlers.
+ // `ev` is the native mouse event that began the interaction.
+ unselect: function(ev) {
+ if (this.isSelected) {
+ this.isSelected = false;
+ if (this.destroySelection) {
+ this.destroySelection(); // TODO: deprecate
+ }
+ this.unrenderSelection();
+ this.trigger('unselect', null, ev);
+ }
+ },
+
+
+ // Unrenders a visual indication of selection
+ unrenderSelection: function() {
+ // subclasses should implement
+ },
+
+
+ /* Event Selection
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ selectEvent: function(event) {
+ if (!this.selectedEvent || this.selectedEvent !== event) {
+ this.unselectEvent();
+ this.renderedEventSegEach(function(seg) {
+ seg.el.addClass('fc-selected');
+ }, event);
+ this.selectedEvent = event;
+ }
+ },
+
+
+ unselectEvent: function() {
+ if (this.selectedEvent) {
+ this.renderedEventSegEach(function(seg) {
+ seg.el.removeClass('fc-selected');
+ }, this.selectedEvent);
+ this.selectedEvent = null;
+ }
+ },
+
+
+ isEventSelected: function(event) {
+ // event references might change on refetchEvents(), while selectedEvent doesn't,
+ // so compare IDs
+ return this.selectedEvent && this.selectedEvent._id === event._id;
+ },
+
+
+ /* Mouse / Touch Unselecting (time range & event unselection)
+ ------------------------------------------------------------------------------------------------------------------*/
+ // TODO: move consistently to down/start or up/end?
+ // TODO: don't kill previous selection if touch scrolling
+
+
+ handleDocumentMousedown: function(ev) {
+ if (isPrimaryMouseButton(ev)) {
+ this.processUnselect(ev);
+ }
+ },
+
+
+ processUnselect: function(ev) {
+ this.processRangeUnselect(ev);
+ this.processEventUnselect(ev);
+ },
+
+
+ processRangeUnselect: function(ev) {
+ var ignore;
+
+ // is there a time-range selection?
+ if (this.isSelected && this.opt('unselectAuto')) {
+ // only unselect if the clicked element is not identical to or inside of an 'unselectCancel' element
+ ignore = this.opt('unselectCancel');
+ if (!ignore || !$(ev.target).closest(ignore).length) {
+ this.unselect(ev);
+ }
+ }
+ },
+
+
+ processEventUnselect: function(ev) {
+ if (this.selectedEvent) {
+ if (!$(ev.target).closest('.fc-selected').length) {
+ this.unselectEvent();
+ }
+ }
+ },
+
+
+ /* Day Click
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Triggers handlers to 'dayClick'
+ // Span has start/end of the clicked area. Only the start is useful.
+ triggerDayClick: function(span, dayEl, ev) {
+ this.trigger(
+ 'dayClick',
+ dayEl,
+ this.calendar.applyTimezone(span.start), // convert to calendar's timezone for external API
+ ev
+ );
+ },
+
+
+ /* Date Utils
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Initializes internal variables related to calculating hidden days-of-week
+ initHiddenDays: function() {
+ var hiddenDays = this.opt('hiddenDays') || []; // array of day-of-week indices that are hidden
+ var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)
+ var dayCnt = 0;
+ var i;
+
+ if (this.opt('weekends') === false) {
+ hiddenDays.push(0, 6); // 0=sunday, 6=saturday
+ }
+
+ for (i = 0; i < 7; i++) {
+ if (
+ !(isHiddenDayHash[i] = $.inArray(i, hiddenDays) !== -1)
+ ) {
+ dayCnt++;
+ }
+ }
+
+ if (!dayCnt) {
+ throw 'invalid hiddenDays'; // all days were hidden? bad.
+ }
+
+ this.isHiddenDayHash = isHiddenDayHash;
+ },
+
+
+ // Is the current day hidden?
+ // `day` is a day-of-week index (0-6), or a Moment
+ isHiddenDay: function(day) {
+ if (moment.isMoment(day)) {
+ day = day.day();
+ }
+ return this.isHiddenDayHash[day];
+ },
+
+
+ // Incrementing the current day until it is no longer a hidden day, returning a copy.
+ // If the initial value of `date` is not a hidden day, don't do anything.
+ // Pass `isExclusive` as `true` if you are dealing with an end date.
+ // `inc` defaults to `1` (increment one day forward each time)
+ skipHiddenDays: function(date, inc, isExclusive) {
+ var out = date.clone();
+ inc = inc || 1;
+ while (
+ this.isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7]
+ ) {
+ out.add(inc, 'days');
+ }
+ return out;
+ },
+
+
+ // Returns the date range of the full days the given range visually appears to occupy.
+ // Returns a new range object.
+ computeDayRange: function(range) {
+ var startDay = range.start.clone().stripTime(); // the beginning of the day the range starts
+ var end = range.end;
+ var endDay = null;
+ var endTimeMS;
+
+ if (end) {
+ endDay = end.clone().stripTime(); // the beginning of the day the range exclusively ends
+ endTimeMS = +end.time(); // # of milliseconds into `endDay`
+
+ // If the end time is actually inclusively part of the next day and is equal to or
+ // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`.
+ // Otherwise, leaving it as inclusive will cause it to exclude `endDay`.
+ if (endTimeMS && endTimeMS >= this.nextDayThreshold) {
+ endDay.add(1, 'days');
+ }
+ }
+
+ // If no end was specified, or if it is within `startDay` but not past nextDayThreshold,
+ // assign the default duration of one day.
+ if (!end || endDay <= startDay) {
+ endDay = startDay.clone().add(1, 'days');
+ }
+
+ return { start: startDay, end: endDay };
+ },
+
+
+ // Does the given event visually appear to occupy more than one day?
+ isMultiDayEvent: function(event) {
+ var range = this.computeDayRange(event); // event is range-ish
+
+ return range.end.diff(range.start, 'days') > 1;
+ }
+
+});
+
+;;
+
+/*
+Embodies a div that has potential scrollbars
+*/
+var Scroller = FC.Scroller = Class.extend({
+
+ el: null, // the guaranteed outer element
+ scrollEl: null, // the element with the scrollbars
+ overflowX: null,
+ overflowY: null,
+
+
+ constructor: function(options) {
+ options = options || {};
+ this.overflowX = options.overflowX || options.overflow || 'auto';
+ this.overflowY = options.overflowY || options.overflow || 'auto';
+ },
+
+
+ render: function() {
+ this.el = this.renderEl();
+ this.applyOverflow();
+ },
+
+
+ renderEl: function() {
+ return (this.scrollEl = $('<div class="fc-scroller"></div>'));
+ },
+
+
+ // sets to natural height, unlocks overflow
+ clear: function() {
+ this.setHeight('auto');
+ this.applyOverflow();
+ },
+
+
+ destroy: function() {
+ this.el.remove();
+ },
+
+
+ // Overflow
+ // -----------------------------------------------------------------------------------------------------------------
+
+
+ applyOverflow: function() {
+ this.scrollEl.css({
+ 'overflow-x': this.overflowX,
+ 'overflow-y': this.overflowY
+ });
+ },
+
+
+ // Causes any 'auto' overflow values to resolves to 'scroll' or 'hidden'.
+ // Useful for preserving scrollbar widths regardless of future resizes.
+ // Can pass in scrollbarWidths for optimization.
+ lockOverflow: function(scrollbarWidths) {
+ var overflowX = this.overflowX;
+ var overflowY = this.overflowY;
+
+ scrollbarWidths = scrollbarWidths || this.getScrollbarWidths();
+
+ if (overflowX === 'auto') {
+ overflowX = (
+ scrollbarWidths.top || scrollbarWidths.bottom || // horizontal scrollbars?
+ // OR scrolling pane with massless scrollbars?
+ this.scrollEl[0].scrollWidth - 1 > this.scrollEl[0].clientWidth
+ // subtract 1 because of IE off-by-one issue
+ ) ? 'scroll' : 'hidden';
+ }
+
+ if (overflowY === 'auto') {
+ overflowY = (
+ scrollbarWidths.left || scrollbarWidths.right || // vertical scrollbars?
+ // OR scrolling pane with massless scrollbars?
+ this.scrollEl[0].scrollHeight - 1 > this.scrollEl[0].clientHeight
+ // subtract 1 because of IE off-by-one issue
+ ) ? 'scroll' : 'hidden';
+ }
+
+ this.scrollEl.css({ 'overflow-x': overflowX, 'overflow-y': overflowY });
+ },
+
+
+ // Getters / Setters
+ // -----------------------------------------------------------------------------------------------------------------
+
+
+ setHeight: function(height) {
+ this.scrollEl.height(height);
+ },
+
+
+ getScrollTop: function() {
+ return this.scrollEl.scrollTop();
+ },
+
+
+ setScrollTop: function(top) {
+ this.scrollEl.scrollTop(top);
+ },
+
+
+ getClientWidth: function() {
+ return this.scrollEl[0].clientWidth;
+ },
+
+
+ getClientHeight: function() {
+ return this.scrollEl[0].clientHeight;
+ },
+
+
+ getScrollbarWidths: function() {
+ return getScrollbarWidths(this.scrollEl);
+ }
+
+});
+
+;;
+
+var Calendar = FC.Calendar = Class.extend({
+
+ dirDefaults: null, // option defaults related to LTR or RTL
+ langDefaults: null, // option defaults related to current locale
+ overrides: null, // option overrides given to the fullCalendar constructor
+ options: null, // all defaults combined with overrides
+ viewSpecCache: null, // cache of view definitions
+ view: null, // current View object
+ header: null,
+ loadingLevel: 0, // number of simultaneous loading tasks
+
+
+ // a lot of this class' OOP logic is scoped within this constructor function,
+ // but in the future, write individual methods on the prototype.
+ constructor: Calendar_constructor,
+
+
+ // Subclasses can override this for initialization logic after the constructor has been called
+ initialize: function() {
+ },
+
+
+ // Initializes `this.options` and other important options-related objects
+ initOptions: function(overrides) {
+ var lang, langDefaults;
+ var isRTL, dirDefaults;
+
+ // converts legacy options into non-legacy ones.
+ // in the future, when this is removed, don't use `overrides` reference. make a copy.
+ overrides = massageOverrides(overrides);
+
+ lang = overrides.lang;
+ langDefaults = langOptionHash[lang];
+ if (!langDefaults) {
+ lang = Calendar.defaults.lang;
+ langDefaults = langOptionHash[lang] || {};
+ }
+
+ isRTL = firstDefined(
+ overrides.isRTL,
+ langDefaults.isRTL,
+ Calendar.defaults.isRTL
+ );
+ dirDefaults = isRTL ? Calendar.rtlDefaults : {};
+
+ this.dirDefaults = dirDefaults;
+ this.langDefaults = langDefaults;
+ this.overrides = overrides;
+ this.options = mergeOptions([ // merge defaults and overrides. lowest to highest precedence
+ Calendar.defaults, // global defaults
+ dirDefaults,
+ langDefaults,
+ overrides
+ ]);
+ populateInstanceComputableOptions(this.options);
+
+ this.viewSpecCache = {}; // somewhat unrelated
+ },
+
+
+ // Gets information about how to create a view. Will use a cache.
+ getViewSpec: function(viewType) {
+ var cache = this.viewSpecCache;
+
+ return cache[viewType] || (cache[viewType] = this.buildViewSpec(viewType));
+ },
+
+
+ // Given a duration singular unit, like "week" or "day", finds a matching view spec.
+ // Preference is given to views that have corresponding buttons.
+ getUnitViewSpec: function(unit) {
+ var viewTypes;
+ var i;
+ var spec;
+
+ if ($.inArray(unit, intervalUnits) != -1) {
+
+ // put views that have buttons first. there will be duplicates, but oh well
+ viewTypes = this.header.getViewsWithButtons();
+ $.each(FC.views, function(viewType) { // all views
+ viewTypes.push(viewType);
+ });
+
+ for (i = 0; i < viewTypes.length; i++) {
+ spec = this.getViewSpec(viewTypes[i]);
+ if (spec) {
+ if (spec.singleUnit == unit) {
+ return spec;
+ }
+ }
+ }
+ }
+ },
+
+
+ // Builds an object with information on how to create a given view
+ buildViewSpec: function(requestedViewType) {
+ var viewOverrides = this.overrides.views || {};
+ var specChain = []; // for the view. lowest to highest priority
+ var defaultsChain = []; // for the view. lowest to highest priority
+ var overridesChain = []; // for the view. lowest to highest priority
+ var viewType = requestedViewType;
+ var spec; // for the view
+ var overrides; // for the view
+ var duration;
+ var unit;
+
+ // iterate from the specific view definition to a more general one until we hit an actual View class
+ while (viewType) {
+ spec = fcViews[viewType];
+ overrides = viewOverrides[viewType];
+ viewType = null; // clear. might repopulate for another iteration
+
+ if (typeof spec === 'function') { // TODO: deprecate
+ spec = { 'class': spec };
+ }
+
+ if (spec) {
+ specChain.unshift(spec);
+ defaultsChain.unshift(spec.defaults || {});
+ duration = duration || spec.duration;
+ viewType = viewType || spec.type;
+ }
+
+ if (overrides) {
+ overridesChain.unshift(overrides); // view-specific option hashes have options at zero-level
+ duration = duration || overrides.duration;
+ viewType = viewType || overrides.type;
+ }
+ }
+
+ spec = mergeProps(specChain);
+ spec.type = requestedViewType;
+ if (!spec['class']) {
+ return false;
+ }
+
+ if (duration) {
+ duration = moment.duration(duration);
+ if (duration.valueOf()) { // valid?
+ spec.duration = duration;
+ unit = computeIntervalUnit(duration);
+
+ // view is a single-unit duration, like "week" or "day"
+ // incorporate options for this. lowest priority
+ if (duration.as(unit) === 1) {
+ spec.singleUnit = unit;
+ overridesChain.unshift(viewOverrides[unit] || {});
+ }
+ }
+ }
+
+ spec.defaults = mergeOptions(defaultsChain);
+ spec.overrides = mergeOptions(overridesChain);
+
+ this.buildViewSpecOptions(spec);
+ this.buildViewSpecButtonText(spec, requestedViewType);
+
+ return spec;
+ },
+
+
+ // Builds and assigns a view spec's options object from its already-assigned defaults and overrides
+ buildViewSpecOptions: function(spec) {
+ spec.options = mergeOptions([ // lowest to highest priority
+ Calendar.defaults, // global defaults
+ spec.defaults, // view's defaults (from ViewSubclass.defaults)
+ this.dirDefaults,
+ this.langDefaults, // locale and dir take precedence over view's defaults!
+ this.overrides, // calendar's overrides (options given to constructor)
+ spec.overrides // view's overrides (view-specific options)
+ ]);
+ populateInstanceComputableOptions(spec.options);
+ },
+
+
+ // Computes and assigns a view spec's buttonText-related options
+ buildViewSpecButtonText: function(spec, requestedViewType) {
+
+ // given an options object with a possible `buttonText` hash, lookup the buttonText for the
+ // requested view, falling back to a generic unit entry like "week" or "day"
+ function queryButtonText(options) {
+ var buttonText = options.buttonText || {};
+ return buttonText[requestedViewType] ||
+ (spec.singleUnit ? buttonText[spec.singleUnit] : null);
+ }
+
+ // highest to lowest priority
+ spec.buttonTextOverride =
+ queryButtonText(this.overrides) || // constructor-specified buttonText lookup hash takes precedence
+ spec.overrides.buttonText; // `buttonText` for view-specific options is a string
+
+ // highest to lowest priority. mirrors buildViewSpecOptions
+ spec.buttonTextDefault =
+ queryButtonText(this.langDefaults) ||
+ queryButtonText(this.dirDefaults) ||
+ spec.defaults.buttonText || // a single string. from ViewSubclass.defaults
+ queryButtonText(Calendar.defaults) ||
+ (spec.duration ? this.humanizeDuration(spec.duration) : null) || // like "3 days"
+ requestedViewType; // fall back to given view name
+ },
+
+
+ // Given a view name for a custom view or a standard view, creates a ready-to-go View object
+ instantiateView: function(viewType) {
+ var spec = this.getViewSpec(viewType);
+
+ return new spec['class'](this, viewType, spec.options, spec.duration);
+ },
+
+
+ // Returns a boolean about whether the view is okay to instantiate at some point
+ isValidViewType: function(viewType) {
+ return Boolean(this.getViewSpec(viewType));
+ },
+
+
+ // Should be called when any type of async data fetching begins
+ pushLoading: function() {
+ if (!(this.loadingLevel++)) {
+ this.trigger('loading', null, true, this.view);
+ }
+ },
+
+
+ // Should be called when any type of async data fetching completes
+ popLoading: function() {
+ if (!(--this.loadingLevel)) {
+ this.trigger('loading', null, false, this.view);
+ }
+ },
+
+
+ // Given arguments to the select method in the API, returns a span (unzoned start/end and other info)
+ buildSelectSpan: function(zonedStartInput, zonedEndInput) {
+ var start = this.moment(zonedStartInput).stripZone();
+ var end;
+
+ if (zonedEndInput) {
+ end = this.moment(zonedEndInput).stripZone();
+ }
+ else if (start.hasTime()) {
+ end = start.clone().add(this.defaultTimedEventDuration);
+ }
+ else {
+ end = start.clone().add(this.defaultAllDayEventDuration);
+ }
+
+ return { start: start, end: end };
+ }
+
+});
+
+
+Calendar.mixin(EmitterMixin);
+
+
+function Calendar_constructor(element, overrides) {
+ var t = this;
+
+
+ t.initOptions(overrides || {});
+ var options = this.options;
+
+
+ // Exports
+ // -----------------------------------------------------------------------------------
+
+ t.render = render;
+ t.destroy = destroy;
+ t.refetchEvents = refetchEvents;
+ t.reportEvents = reportEvents;
+ t.reportEventChange = reportEventChange;
+ t.rerenderEvents = renderEvents; // `renderEvents` serves as a rerender. an API method
+ t.changeView = renderView; // `renderView` will switch to another view
+ t.select = select;
+ t.unselect = unselect;
+ t.prev = prev;
+ t.next = next;
+ t.prevYear = prevYear;
+ t.nextYear = nextYear;
+ t.today = today;
+ t.gotoDate = gotoDate;
+ t.incrementDate = incrementDate;
+ t.zoomTo = zoomTo;
+ t.getDate = getDate;
+ t.getCalendar = getCalendar;
+ t.getView = getView;
+ t.option = option;
+ t.trigger = trigger;
+
+
+
+ // Language-data Internals
+ // -----------------------------------------------------------------------------------
+ // Apply overrides to the current language's data
+
+
+ var localeData = createObject( // make a cheap copy
+ getMomentLocaleData(options.lang) // will fall back to en
+ );
+
+ if (options.monthNames) {
+ localeData._months = options.monthNames;
+ }
+ if (options.monthNamesShort) {
+ localeData._monthsShort = options.monthNamesShort;
+ }
+ if (options.dayNames) {
+ localeData._weekdays = options.dayNames;
+ }
+ if (options.dayNamesShort) {
+ localeData._weekdaysShort = options.dayNamesShort;
+ }
+ if (options.firstDay != null) {
+ var _week = createObject(localeData._week); // _week: { dow: # }
+ _week.dow = options.firstDay;
+ localeData._week = _week;
+ }
+
+ // assign a normalized value, to be used by our .week() moment extension
+ localeData._fullCalendar_weekCalc = (function(weekCalc) {
+ if (typeof weekCalc === 'function') {
+ return weekCalc;
+ }
+ else if (weekCalc === 'local') {
+ return weekCalc;
+ }
+ else if (weekCalc === 'iso' || weekCalc === 'ISO') {
+ return 'ISO';
+ }
+ })(options.weekNumberCalculation);
+
+
+
+ // Calendar-specific Date Utilities
+ // -----------------------------------------------------------------------------------
+
+
+ t.defaultAllDayEventDuration = moment.duration(options.defaultAllDayEventDuration);
+ t.defaultTimedEventDuration = moment.duration(options.defaultTimedEventDuration);
+
+
+ // Builds a moment using the settings of the current calendar: timezone and language.
+ // Accepts anything the vanilla moment() constructor accepts.
+ t.moment = function() {
+ var mom;
+
+ if (options.timezone === 'local') {
+ mom = FC.moment.apply(null, arguments);
+
+ // Force the moment to be local, because FC.moment doesn't guarantee it.
+ if (mom.hasTime()) { // don't give ambiguously-timed moments a local zone
+ mom.local();
+ }
+ }
+ else if (options.timezone === 'UTC') {
+ mom = FC.moment.utc.apply(null, arguments); // process as UTC
+ }
+ else {
+ mom = FC.moment.parseZone.apply(null, arguments); // let the input decide the zone
+ }
+
+ if ('_locale' in mom) { // moment 2.8 and above
+ mom._locale = localeData;
+ }
+ else { // pre-moment-2.8
+ mom._lang = localeData;
+ }
+
+ return mom;
+ };
+
+
+ // Returns a boolean about whether or not the calendar knows how to calculate
+ // the timezone offset of arbitrary dates in the current timezone.
+ t.getIsAmbigTimezone = function() {
+ return options.timezone !== 'local' && options.timezone !== 'UTC';
+ };
+
+
+ // Returns a copy of the given date in the current timezone. Has no effect on dates without times.
+ t.applyTimezone = function(date) {
+ if (!date.hasTime()) {
+ return date.clone();
+ }
+
+ var zonedDate = t.moment(date.toArray());
+ var timeAdjust = date.time() - zonedDate.time();
+ var adjustedZonedDate;
+
+ // Safari sometimes has problems with this coersion when near DST. Adjust if necessary. (bug #2396)
+ if (timeAdjust) { // is the time result different than expected?
+ adjustedZonedDate = zonedDate.clone().add(timeAdjust); // add milliseconds
+ if (date.time() - adjustedZonedDate.time() === 0) { // does it match perfectly now?
+ zonedDate = adjustedZonedDate;
+ }
+ }
+
+ return zonedDate;
+ };
+
+
+ // Returns a moment for the current date, as defined by the client's computer or from the `now` option.
+ // Will return an moment with an ambiguous timezone.
+ t.getNow = function() {
+ var now = options.now;
+ if (typeof now === 'function') {
+ now = now();
+ }
+ return t.moment(now).stripZone();
+ };
+
+
+ // Get an event's normalized end date. If not present, calculate it from the defaults.
+ t.getEventEnd = function(event) {
+ if (event.end) {
+ return event.end.clone();
+ }
+ else {
+ return t.getDefaultEventEnd(event.allDay, event.start);
+ }
+ };
+
+
+ // Given an event's allDay status and start date, return what its fallback end date should be.
+ // TODO: rename to computeDefaultEventEnd
+ t.getDefaultEventEnd = function(allDay, zonedStart) {
+ var end = zonedStart.clone();
+
+ if (allDay) {
+ end.stripTime().add(t.defaultAllDayEventDuration);
+ }
+ else {
+ end.add(t.defaultTimedEventDuration);
+ }
+
+ if (t.getIsAmbigTimezone()) {
+ end.stripZone(); // we don't know what the tzo should be
+ }
+
+ return end;
+ };
+
+
+ // Produces a human-readable string for the given duration.
+ // Side-effect: changes the locale of the given duration.
+ t.humanizeDuration = function(duration) {
+ return (duration.locale || duration.lang).call(duration, options.lang) // works moment-pre-2.8
+ .humanize();
+ };
+
+
+
+ // Imports
+ // -----------------------------------------------------------------------------------
+
+
+ EventManager.call(t, options);
+ var isFetchNeeded = t.isFetchNeeded;
+ var fetchEvents = t.fetchEvents;
+
+
+
+ // Locals
+ // -----------------------------------------------------------------------------------
+
+
+ var _element = element[0];
+ var header;
+ var headerElement;
+ var content;
+ var tm; // for making theme classes
+ var currentView; // NOTE: keep this in sync with this.view
+ var viewsByType = {}; // holds all instantiated view instances, current or not
+ var suggestedViewHeight;
+ var windowResizeProxy; // wraps the windowResize function
+ var ignoreWindowResize = 0;
+ var events = [];
+ var date; // unzoned
+
+
+
+ // Main Rendering
+ // -----------------------------------------------------------------------------------
+
+
+ // compute the initial ambig-timezone date
+ if (options.defaultDate != null) {
+ date = t.moment(options.defaultDate).stripZone();
+ }
+ else {
+ date = t.getNow(); // getNow already returns unzoned
+ }
+
+
+ function render() {
+ if (!content) {
+ initialRender();
+ }
+ else if (elementVisible()) {
+ // mainly for the public API
+ calcSize();
+ renderView();
+ }
+ }
+
+
+ function initialRender() {
+ tm = options.theme ? 'ui' : 'fc';
+ element.addClass('fc');
+
+ if (options.isRTL) {
+ element.addClass('fc-rtl');
+ }
+ else {
+ element.addClass('fc-ltr');
+ }
+
+ if (options.theme) {
+ element.addClass('ui-widget');
+ }
+ else {
+ element.addClass('fc-unthemed');
+ }
+
+ content = $("<div class='fc-view-container'/>").prependTo(element);
+
+ header = t.header = new Header(t, options);
+ headerElement = header.render();
+ if (headerElement) {
+ element.prepend(headerElement);
+ }
+
+ renderView(options.defaultView);
+
+ if (options.handleWindowResize) {
+ windowResizeProxy = debounce(windowResize, options.windowResizeDelay); // prevents rapid calls
+ $(window).resize(windowResizeProxy);
+ }
+ }
+
+
+ function destroy() {
+
+ if (currentView) {
+ currentView.removeElement();
+
+ // NOTE: don't null-out currentView/t.view in case API methods are called after destroy.
+ // It is still the "current" view, just not rendered.
+ }
+
+ header.removeElement();
+ content.remove();
+ element.removeClass('fc fc-ltr fc-rtl fc-unthemed ui-widget');
+
+ if (windowResizeProxy) {
+ $(window).unbind('resize', windowResizeProxy);
+ }
+ }
+
+
+ function elementVisible() {
+ return element.is(':visible');
+ }
+
+
+
+ // View Rendering
+ // -----------------------------------------------------------------------------------
+
+
+ // Renders a view because of a date change, view-type change, or for the first time.
+ // If not given a viewType, keep the current view but render different dates.
+ function renderView(viewType) {
+ ignoreWindowResize++;
+
+ // if viewType is changing, remove the old view's rendering
+ if (currentView && viewType && currentView.type !== viewType) {
+ header.deactivateButton(currentView.type);
+ freezeContentHeight(); // prevent a scroll jump when view element is removed
+ currentView.removeElement();
+ currentView = t.view = null;
+ }
+
+ // if viewType changed, or the view was never created, create a fresh view
+ if (!currentView && viewType) {
+ currentView = t.view =
+ viewsByType[viewType] ||
+ (viewsByType[viewType] = t.instantiateView(viewType));
+
+ currentView.setElement(
+ $("<div class='fc-view fc-" + viewType + "-view' />").appendTo(content)
+ );
+ header.activateButton(viewType);
+ }
+
+ if (currentView) {
+
+ // in case the view should render a period of time that is completely hidden
+ date = currentView.massageCurrentDate(date);
+
+ // render or rerender the view
+ if (
+ !currentView.displaying ||
+ !date.isWithin(currentView.intervalStart, currentView.intervalEnd) // implicit date window change
+ ) {
+ if (elementVisible()) {
+
+ currentView.display(date); // will call freezeContentHeight
+ unfreezeContentHeight(); // immediately unfreeze regardless of whether display is async
+
+ // need to do this after View::render, so dates are calculated
+ updateHeaderTitle();
+ updateTodayButton();
+
+ getAndRenderEvents();
+ }
+ }
+ }
+
+ unfreezeContentHeight(); // undo any lone freezeContentHeight calls
+ ignoreWindowResize--;
+ }
+
+
+
+ // Resizing
+ // -----------------------------------------------------------------------------------
+
+
+ t.getSuggestedViewHeight = function() {
+ if (suggestedViewHeight === undefined) {
+ calcSize();
+ }
+ return suggestedViewHeight;
+ };
+
+
+ t.isHeightAuto = function() {
+ return options.contentHeight === 'auto' || options.height === 'auto';
+ };
+
+
+ function updateSize(shouldRecalc) {
+ if (elementVisible()) {
+
+ if (shouldRecalc) {
+ _calcSize();
+ }
+
+ ignoreWindowResize++;
+ currentView.updateSize(true); // isResize=true. will poll getSuggestedViewHeight() and isHeightAuto()
+ ignoreWindowResize--;
+
+ return true; // signal success
+ }
+ }
+
+
+ function calcSize() {
+ if (elementVisible()) {
+ _calcSize();
+ }
+ }
+
+
+ function _calcSize() { // assumes elementVisible
+ if (typeof options.contentHeight === 'number') { // exists and not 'auto'
+ suggestedViewHeight = options.contentHeight;
+ }
+ else if (typeof options.height === 'number') { // exists and not 'auto'
+ suggestedViewHeight = options.height - (headerElement ? headerElement.outerHeight(true) : 0);
+ }
+ else {
+ suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
+ }
+ }
+
+
+ function windowResize(ev) {
+ if (
+ !ignoreWindowResize &&
+ ev.target === window && // so we don't process jqui "resize" events that have bubbled up
+ currentView.start // view has already been rendered
+ ) {
+ if (updateSize(true)) {
+ currentView.trigger('windowResize', _element);
+ }
+ }
+ }
+
+
+
+ /* Event Fetching/Rendering
+ -----------------------------------------------------------------------------*/
+ // TODO: going forward, most of this stuff should be directly handled by the view
+
+
+ function refetchEvents() { // can be called as an API method
+ destroyEvents(); // so that events are cleared before user starts waiting for AJAX
+ fetchAndRenderEvents();
+ }
+
+
+ function renderEvents() { // destroys old events if previously rendered
+ if (elementVisible()) {
+ freezeContentHeight();
+ currentView.displayEvents(events);
+ unfreezeContentHeight();
+ }
+ }
+
+
+ function destroyEvents() {
+ freezeContentHeight();
+ currentView.clearEvents();
+ unfreezeContentHeight();
+ }
+
+
+ function getAndRenderEvents() {
+ if (!options.lazyFetching || isFetchNeeded(currentView.start, currentView.end)) {
+ fetchAndRenderEvents();
+ }
+ else {
+ renderEvents();
+ }
+ }
+
+
+ function fetchAndRenderEvents() {
+ fetchEvents(currentView.start, currentView.end);
+ // ... will call reportEvents
+ // ... which will call renderEvents
+ }
+
+
+ // called when event data arrives
+ function reportEvents(_events) {
+ events = _events;
+ renderEvents();
+ }
+
+
+ // called when a single event's data has been changed
+ function reportEventChange() {
+ renderEvents();
+ }
+
+
+
+ /* Header Updating
+ -----------------------------------------------------------------------------*/
+
+
+ function updateHeaderTitle() {
+ header.updateTitle(currentView.title);
+ }
+
+
+ function updateTodayButton() {
+ var now = t.getNow();
+ if (now.isWithin(currentView.intervalStart, currentView.intervalEnd)) {
+ header.disableButton('today');
+ }
+ else {
+ header.enableButton('today');
+ }
+ }
+
+
+
+ /* Selection
+ -----------------------------------------------------------------------------*/
+
+
+ // this public method receives start/end dates in any format, with any timezone
+ function select(zonedStartInput, zonedEndInput) {
+ currentView.select(
+ t.buildSelectSpan.apply(t, arguments)
+ );
+ }
+
+
+ function unselect() { // safe to be called before renderView
+ if (currentView) {
+ currentView.unselect();
+ }
+ }
+
+
+
+ /* Date
+ -----------------------------------------------------------------------------*/
+
+
+ function prev() {
+ date = currentView.computePrevDate(date);
+ renderView();
+ }
+
+
+ function next() {
+ date = currentView.computeNextDate(date);
+ renderView();
+ }
+
+
+ function prevYear() {
+ date.add(-1, 'years');
+ renderView();
+ }
+
+
+ function nextYear() {
+ date.add(1, 'years');
+ renderView();
+ }
+
+
+ function today() {
+ date = t.getNow();
+ renderView();
+ }
+
+
+ function gotoDate(zonedDateInput) {
+ date = t.moment(zonedDateInput).stripZone();
+ renderView();
+ }
+
+
+ function incrementDate(delta) {
+ date.add(moment.duration(delta));
+ renderView();
+ }
+
+
+ // Forces navigation to a view for the given date.
+ // `viewType` can be a specific view name or a generic one like "week" or "day".
+ function zoomTo(newDate, viewType) {
+ var spec;
+
+ viewType = viewType || 'day'; // day is default zoom
+ spec = t.getViewSpec(viewType) || t.getUnitViewSpec(viewType);
+
+ date = newDate.clone();
+ renderView(spec ? spec.type : null);
+ }
+
+
+ // for external API
+ function getDate() {
+ return t.applyTimezone(date); // infuse the calendar's timezone
+ }
+
+
+
+ /* Height "Freezing"
+ -----------------------------------------------------------------------------*/
+ // TODO: move this into the view
+
+ t.freezeContentHeight = freezeContentHeight;
+ t.unfreezeContentHeight = unfreezeContentHeight;
+
+
+ function freezeContentHeight() {
+ content.css({
+ width: '100%',
+ height: content.height(),
+ overflow: 'hidden'
+ });
+ }
+
+
+ function unfreezeContentHeight() {
+ content.css({
+ width: '',
+ height: '',
+ overflow: ''
+ });
+ }
+
+
+
+ /* Misc
+ -----------------------------------------------------------------------------*/
+
+
+ function getCalendar() {
+ return t;
+ }
+
+
+ function getView() {
+ return currentView;
+ }
+
+
+ function option(name, value) {
+ if (value === undefined) {
+ return options[name];
+ }
+ if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
+ options[name] = value;
+ updateSize(true); // true = allow recalculation of height
+ }
+ }
+
+
+ function trigger(name, thisObj) { // overrides the Emitter's trigger method :(
+ var args = Array.prototype.slice.call(arguments, 2);
+
+ thisObj = thisObj || _element;
+ this.triggerWith(name, thisObj, args); // Emitter's method
+
+ if (options[name]) {
+ return options[name].apply(thisObj, args);
+ }
+ }
+
+ t.initialize();
+}
+
+;;
+
+Calendar.defaults = {
+
+ titleRangeSeparator: ' \u2014 ', // emphasized dash
+ monthYearFormat: 'MMMM YYYY', // required for en. other languages rely on datepicker computable option
+
+ defaultTimedEventDuration: '02:00:00',
+ defaultAllDayEventDuration: { days: 1 },
+ forceEventDuration: false,
+ nextDayThreshold: '09:00:00', // 9am
+
+ // display
+ defaultView: 'month',
+ aspectRatio: 1.35,
+ header: {
+ left: 'title',
+ center: '',
+ right: 'today prev,next'
+ },
+ weekends: true,
+ weekNumbers: false,
+
+ weekNumberTitle: 'W',
+ weekNumberCalculation: 'local',
+
+ //editable: false,
+
+ //nowIndicator: false,
+
+ scrollTime: '06:00:00',
+
+ // event ajax
+ lazyFetching: true,
+ startParam: 'start',
+ endParam: 'end',
+ timezoneParam: 'timezone',
+
+ timezone: false,
+
+ //allDayDefault: undefined,
+
+ // locale
+ isRTL: false,
+ buttonText: {
+ prev: "prev",
+ next: "next",
+ prevYear: "prev year",
+ nextYear: "next year",
+ year: 'year', // TODO: locale files need to specify this
+ today: 'today',
+ month: 'month',
+ week: 'week',
+ day: 'day'
+ },
+
+ buttonIcons: {
+ prev: 'left-single-arrow',
+ next: 'right-single-arrow',
+ prevYear: 'left-double-arrow',
+ nextYear: 'right-double-arrow'
+ },
+
+ // jquery-ui theming
+ theme: false,
+ themeButtonIcons: {
+ prev: 'circle-triangle-w',
+ next: 'circle-triangle-e',
+ prevYear: 'seek-prev',
+ nextYear: 'seek-next'
+ },
+
+ //eventResizableFromStart: false,
+ dragOpacity: .75,
+ dragRevertDuration: 500,
+ dragScroll: true,
+
+ //selectable: false,
+ unselectAuto: true,
+
+ dropAccept: '*',
+
+ eventOrder: 'title',
+
+ eventLimit: false,
+ eventLimitText: 'more',
+ eventLimitClick: 'popover',
+ dayPopoverFormat: 'LL',
+
+ handleWindowResize: true,
+ windowResizeDelay: 200, // milliseconds before an updateSize happens
+
+ longPressDelay: 1000
+
+};
+
+
+Calendar.englishDefaults = { // used by lang.js
+ dayPopoverFormat: 'dddd, MMMM D'
+};
+
+
+Calendar.rtlDefaults = { // right-to-left defaults
+ header: { // TODO: smarter solution (first/center/last ?)
+ left: 'next,prev today',
+ center: '',
+ right: 'title'
+ },
+ buttonIcons: {
+ prev: 'right-single-arrow',
+ next: 'left-single-arrow',
+ prevYear: 'right-double-arrow',
+ nextYear: 'left-double-arrow'
+ },
+ themeButtonIcons: {
+ prev: 'circle-triangle-e',
+ next: 'circle-triangle-w',
+ nextYear: 'seek-prev',
+ prevYear: 'seek-next'
+ }
+};
+
+;;
+
+var langOptionHash = FC.langs = {}; // initialize and expose
+
+
+// TODO: document the structure and ordering of a FullCalendar lang file
+// TODO: rename everything "lang" to "locale", like what the moment project did
+
+
+// Initialize jQuery UI datepicker translations while using some of the translations
+// Will set this as the default language for datepicker.
+FC.datepickerLang = function(langCode, dpLangCode, dpOptions) {
+
+ // get the FullCalendar internal option hash for this language. create if necessary
+ var fcOptions = langOptionHash[langCode] || (langOptionHash[langCode] = {});
+
+ // transfer some simple options from datepicker to fc
+ fcOptions.isRTL = dpOptions.isRTL;
+ fcOptions.weekNumberTitle = dpOptions.weekHeader;
+
+ // compute some more complex options from datepicker
+ $.each(dpComputableOptions, function(name, func) {
+ fcOptions[name] = func(dpOptions);
+ });
+
+ // is jQuery UI Datepicker is on the page?
+ if ($.datepicker) {
+
+ // Register the language data.
+ // FullCalendar and MomentJS use language codes like "pt-br" but Datepicker
+ // does it like "pt-BR" or if it doesn't have the language, maybe just "pt".
+ // Make an alias so the language can be referenced either way.
+ $.datepicker.regional[dpLangCode] =
+ $.datepicker.regional[langCode] = // alias
+ dpOptions;
+
+ // Alias 'en' to the default language data. Do this every time.
+ $.datepicker.regional.en = $.datepicker.regional[''];
+
+ // Set as Datepicker's global defaults.
+ $.datepicker.setDefaults(dpOptions);
+ }
+};
+
+
+// Sets FullCalendar-specific translations. Will set the language as the global default.
+FC.lang = function(langCode, newFcOptions) {
+ var fcOptions;
+ var momOptions;
+
+ // get the FullCalendar internal option hash for this language. create if necessary
+ fcOptions = langOptionHash[langCode] || (langOptionHash[langCode] = {});
+
+ // provided new options for this language? merge them in
+ if (newFcOptions) {
+ fcOptions = langOptionHash[langCode] = mergeOptions([ fcOptions, newFcOptions ]);
+ }
+
+ // compute language options that weren't defined.
+ // always do this. newFcOptions can be undefined when initializing from i18n file,
+ // so no way to tell if this is an initialization or a default-setting.
+ momOptions = getMomentLocaleData(langCode); // will fall back to en
+ $.each(momComputableOptions, function(name, func) {
+ if (fcOptions[name] == null) {
+ fcOptions[name] = func(momOptions, fcOptions);
+ }
+ });
+
+ // set it as the default language for FullCalendar
+ Calendar.defaults.lang = langCode;
+};
+
+
+// NOTE: can't guarantee any of these computations will run because not every language has datepicker
+// configs, so make sure there are English fallbacks for these in the defaults file.
+var dpComputableOptions = {
+
+ buttonText: function(dpOptions) {
+ return {
+ // the translations sometimes wrongly contain HTML entities
+ prev: stripHtmlEntities(dpOptions.prevText),
+ next: stripHtmlEntities(dpOptions.nextText),
+ today: stripHtmlEntities(dpOptions.currentText)
+ };
+ },
+
+ // Produces format strings like "MMMM YYYY" -> "September 2014"
+ monthYearFormat: function(dpOptions) {
+ return dpOptions.showMonthAfterYear ?
+ 'YYYY[' + dpOptions.yearSuffix + '] MMMM' :
+ 'MMMM YYYY[' + dpOptions.yearSuffix + ']';
+ }
+
+};
+
+var momComputableOptions = {
+
+ // Produces format strings like "ddd M/D" -> "Fri 9/15"
+ dayOfMonthFormat: function(momOptions, fcOptions) {
+ var format = momOptions.longDateFormat('l'); // for the format like "M/D/YYYY"
+
+ // strip the year off the edge, as well as other misc non-whitespace chars
+ format = format.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g, '');
+
+ if (fcOptions.isRTL) {
+ format += ' ddd'; // for RTL, add day-of-week to end
+ }
+ else {
+ format = 'ddd ' + format; // for LTR, add day-of-week to beginning
+ }
+ return format;
+ },
+
+ // Produces format strings like "h:mma" -> "6:00pm"
+ mediumTimeFormat: function(momOptions) { // can't be called `timeFormat` because collides with option
+ return momOptions.longDateFormat('LT')
+ .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
+ },
+
+ // Produces format strings like "h(:mm)a" -> "6pm" / "6:30pm"
+ smallTimeFormat: function(momOptions) {
+ return momOptions.longDateFormat('LT')
+ .replace(':mm', '(:mm)')
+ .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
+ .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
+ },
+
+ // Produces format strings like "h(:mm)t" -> "6p" / "6:30p"
+ extraSmallTimeFormat: function(momOptions) {
+ return momOptions.longDateFormat('LT')
+ .replace(':mm', '(:mm)')
+ .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
+ .replace(/\s*a$/i, 't'); // convert to AM/PM/am/pm to lowercase one-letter. remove any spaces beforehand
+ },
+
+ // Produces format strings like "ha" / "H" -> "6pm" / "18"
+ hourFormat: function(momOptions) {
+ return momOptions.longDateFormat('LT')
+ .replace(':mm', '')
+ .replace(/(\Wmm)$/, '') // like above, but for foreign langs
+ .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
+ },
+
+ // Produces format strings like "h:mm" -> "6:30" (with no AM/PM)
+ noMeridiemTimeFormat: function(momOptions) {
+ return momOptions.longDateFormat('LT')
+ .replace(/\s*a$/i, ''); // remove trailing AM/PM
+ }
+
+};
+
+
+// options that should be computed off live calendar options (considers override options)
+// TODO: best place for this? related to lang?
+// TODO: flipping text based on isRTL is a bad idea because the CSS `direction` might want to handle it
+var instanceComputableOptions = {
+
+ // Produces format strings for results like "Mo 16"
+ smallDayDateFormat: function(options) {
+ return options.isRTL ?
+ 'D dd' :
+ 'dd D';
+ },
+
+ // Produces format strings for results like "Wk 5"
+ weekFormat: function(options) {
+ return options.isRTL ?
+ 'w[ ' + options.weekNumberTitle + ']' :
+ '[' + options.weekNumberTitle + ' ]w';
+ },
+
+ // Produces format strings for results like "Wk5"
+ smallWeekFormat: function(options) {
+ return options.isRTL ?
+ 'w[' + options.weekNumberTitle + ']' :
+ '[' + options.weekNumberTitle + ']w';
+ }
+
+};
+
+function populateInstanceComputableOptions(options) {
+ $.each(instanceComputableOptions, function(name, func) {
+ if (options[name] == null) {
+ options[name] = func(options);
+ }
+ });
+}
+
+
+// Returns moment's internal locale data. If doesn't exist, returns English.
+// Works with moment-pre-2.8
+function getMomentLocaleData(langCode) {
+ var func = moment.localeData || moment.langData;
+ return func.call(moment, langCode) ||
+ func.call(moment, 'en'); // the newer localData could return null, so fall back to en
+}
+
+
+// Initialize English by forcing computation of moment-derived options.
+// Also, sets it as the default.
+FC.lang('en', Calendar.englishDefaults);
+
+;;
+
+/* Top toolbar area with buttons and title
+----------------------------------------------------------------------------------------------------------------------*/
+// TODO: rename all header-related things to "toolbar"
+
+function Header(calendar, options) {
+ var t = this;
+
+ // exports
+ t.render = render;
+ t.removeElement = removeElement;
+ t.updateTitle = updateTitle;
+ t.activateButton = activateButton;
+ t.deactivateButton = deactivateButton;
+ t.disableButton = disableButton;
+ t.enableButton = enableButton;
+ t.getViewsWithButtons = getViewsWithButtons;
+
+ // locals
+ var el = $();
+ var viewsWithButtons = [];
+ var tm;
+
+
+ function render() {
+ var sections = options.header;
+
+ tm = options.theme ? 'ui' : 'fc';
+
+ if (sections) {
+ el = $("<div class='fc-toolbar'/>")
+ .append(renderSection('left'))
+ .append(renderSection('right'))
+ .append(renderSection('center'))
+ .append('<div class="fc-clear"/>');
+
+ return el;
+ }
+ }
+
+
+ function removeElement() {
+ el.remove();
+ el = $();
+ }
+
+
+ function renderSection(position) {
+ var sectionEl = $('<div class="fc-' + position + '"/>');
+ var buttonStr = options.header[position];
+
+ if (buttonStr) {
+ $.each(buttonStr.split(' '), function(i) {
+ var groupChildren = $();
+ var isOnlyButtons = true;
+ var groupEl;
+
+ $.each(this.split(','), function(j, buttonName) {
+ var customButtonProps;
+ var viewSpec;
+ var buttonClick;
+ var overrideText; // text explicitly set by calendar's constructor options. overcomes icons
+ var defaultText;
+ var themeIcon;
+ var normalIcon;
+ var innerHtml;
+ var classes;
+ var button; // the element
+
+ if (buttonName == 'title') {
+ groupChildren = groupChildren.add($('<h2>&nbsp;</h2>')); // we always want it to take up height
+ isOnlyButtons = false;
+ }
+ else {
+ if ((customButtonProps = (calendar.options.customButtons || {})[buttonName])) {
+ buttonClick = function(ev) {
+ if (customButtonProps.click) {
+ customButtonProps.click.call(button[0], ev);
+ }
+ };
+ overrideText = ''; // icons will override text
+ defaultText = customButtonProps.text;
+ }
+ else if ((viewSpec = calendar.getViewSpec(buttonName))) {
+ buttonClick = function() {
+ calendar.changeView(buttonName);
+ };
+ viewsWithButtons.push(buttonName);
+ overrideText = viewSpec.buttonTextOverride;
+ defaultText = viewSpec.buttonTextDefault;
+ }
+ else if (calendar[buttonName]) { // a calendar method
+ buttonClick = function() {
+ calendar[buttonName]();
+ };
+ overrideText = (calendar.overrides.buttonText || {})[buttonName];
+ defaultText = options.buttonText[buttonName]; // everything else is considered default
+ }
+
+ if (buttonClick) {
+
+ themeIcon =
+ customButtonProps ?
+ customButtonProps.themeIcon :
+ options.themeButtonIcons[buttonName];
+
+ normalIcon =
+ customButtonProps ?
+ customButtonProps.icon :
+ options.buttonIcons[buttonName];
+
+ if (overrideText) {
+ innerHtml = htmlEscape(overrideText);
+ }
+ else if (themeIcon && options.theme) {
+ innerHtml = "<span class='ui-icon ui-icon-" + themeIcon + "'></span>";
+ }
+ else if (normalIcon && !options.theme) {
+ innerHtml = "<span class='fc-icon fc-icon-" + normalIcon + "'></span>";
+ }
+ else {
+ innerHtml = htmlEscape(defaultText);
+ }
+
+ classes = [
+ 'fc-' + buttonName + '-button',
+ tm + '-button',
+ tm + '-state-default'
+ ];
+
+ button = $( // type="button" so that it doesn't submit a form
+ '<button type="button" class="' + classes.join(' ') + '">' +
+ innerHtml +
+ '</button>'
+ )
+ .click(function(ev) {
+ // don't process clicks for disabled buttons
+ if (!button.hasClass(tm + '-state-disabled')) {
+
+ buttonClick(ev);
+
+ // after the click action, if the button becomes the "active" tab, or disabled,
+ // it should never have a hover class, so remove it now.
+ if (
+ button.hasClass(tm + '-state-active') ||
+ button.hasClass(tm + '-state-disabled')
+ ) {
+ button.removeClass(tm + '-state-hover');
+ }
+ }
+ })
+ .mousedown(function() {
+ // the *down* effect (mouse pressed in).
+ // only on buttons that are not the "active" tab, or disabled
+ button
+ .not('.' + tm + '-state-active')
+ .not('.' + tm + '-state-disabled')
+ .addClass(tm + '-state-down');
+ })
+ .mouseup(function() {
+ // undo the *down* effect
+ button.removeClass(tm + '-state-down');
+ })
+ .hover(
+ function() {
+ // the *hover* effect.
+ // only on buttons that are not the "active" tab, or disabled
+ button
+ .not('.' + tm + '-state-active')
+ .not('.' + tm + '-state-disabled')
+ .addClass(tm + '-state-hover');
+ },
+ function() {
+ // undo the *hover* effect
+ button
+ .removeClass(tm + '-state-hover')
+ .removeClass(tm + '-state-down'); // if mouseleave happens before mouseup
+ }
+ );
+
+ groupChildren = groupChildren.add(button);
+ }
+ }
+ });
+
+ if (isOnlyButtons) {
+ groupChildren
+ .first().addClass(tm + '-corner-left').end()
+ .last().addClass(tm + '-corner-right').end();
+ }
+
+ if (groupChildren.length > 1) {
+ groupEl = $('<div/>');
+ if (isOnlyButtons) {
+ groupEl.addClass('fc-button-group');
+ }
+ groupEl.append(groupChildren);
+ sectionEl.append(groupEl);
+ }
+ else {
+ sectionEl.append(groupChildren); // 1 or 0 children
+ }
+ });
+ }
+
+ return sectionEl;
+ }
+
+
+ function updateTitle(text) {
+ el.find('h2').text(text);
+ }
+
+
+ function activateButton(buttonName) {
+ el.find('.fc-' + buttonName + '-button')
+ .addClass(tm + '-state-active');
+ }
+
+
+ function deactivateButton(buttonName) {
+ el.find('.fc-' + buttonName + '-button')
+ .removeClass(tm + '-state-active');
+ }
+
+
+ function disableButton(buttonName) {
+ el.find('.fc-' + buttonName + '-button')
+ .attr('disabled', 'disabled')
+ .addClass(tm + '-state-disabled');
+ }
+
+
+ function enableButton(buttonName) {
+ el.find('.fc-' + buttonName + '-button')
+ .removeAttr('disabled')
+ .removeClass(tm + '-state-disabled');
+ }
+
+
+ function getViewsWithButtons() {
+ return viewsWithButtons;
+ }
+
+}
+
+;;
+
+FC.sourceNormalizers = [];
+FC.sourceFetchers = [];
+
+var ajaxDefaults = {
+ dataType: 'json',
+ cache: false
+};
+
+var eventGUID = 1;
+
+
+function EventManager(options) { // assumed to be a calendar
+ var t = this;
+
+
+ // exports
+ t.isFetchNeeded = isFetchNeeded;
+ t.fetchEvents = fetchEvents;
+ t.addEventSource = addEventSource;
+ t.removeEventSource = removeEventSource;
+ t.updateEvent = updateEvent;
+ t.renderEvent = renderEvent;
+ t.removeEvents = removeEvents;
+ t.clientEvents = clientEvents;
+ t.mutateEvent = mutateEvent;
+ t.normalizeEventDates = normalizeEventDates;
+ t.normalizeEventTimes = normalizeEventTimes;
+
+
+ // imports
+ var reportEvents = t.reportEvents;
+
+
+ // locals
+ var stickySource = { events: [] };
+ var sources = [ stickySource ];
+ var rangeStart, rangeEnd;
+ var currentFetchID = 0;
+ var pendingSourceCnt = 0;
+ var cache = []; // holds events that have already been expanded
+
+
+ $.each(
+ (options.events ? [ options.events ] : []).concat(options.eventSources || []),
+ function(i, sourceInput) {
+ var source = buildEventSource(sourceInput);
+ if (source) {
+ sources.push(source);
+ }
+ }
+ );
+
+
+
+ /* Fetching
+ -----------------------------------------------------------------------------*/
+
+
+ // start and end are assumed to be unzoned
+ function isFetchNeeded(start, end) {
+ return !rangeStart || // nothing has been fetched yet?
+ start < rangeStart || end > rangeEnd; // is part of the new range outside of the old range?
+ }
+
+
+ function fetchEvents(start, end) {
+ rangeStart = start;
+ rangeEnd = end;
+ cache = [];
+ var fetchID = ++currentFetchID;
+ var len = sources.length;
+ pendingSourceCnt = len;
+ for (var i=0; i<len; i++) {
+ fetchEventSource(sources[i], fetchID);
+ }
+ }
+
+
+ function fetchEventSource(source, fetchID) {
+ _fetchEventSource(source, function(eventInputs) {
+ var isArraySource = $.isArray(source.events);
+ var i, eventInput;
+ var abstractEvent;
+
+ if (fetchID == currentFetchID) {
+
+ if (eventInputs) {
+ for (i = 0; i < eventInputs.length; i++) {
+ eventInput = eventInputs[i];
+
+ if (isArraySource) { // array sources have already been convert to Event Objects
+ abstractEvent = eventInput;
+ }
+ else {
+ abstractEvent = buildEventFromInput(eventInput, source);
+ }
+
+ if (abstractEvent) { // not false (an invalid event)
+ cache.push.apply(
+ cache,
+ expandEvent(abstractEvent) // add individual expanded events to the cache
+ );
+ }
+ }
+ }
+
+ pendingSourceCnt--;
+ if (!pendingSourceCnt) {
+ reportEvents(cache);
+ }
+ }
+ });
+ }
+
+
+ function _fetchEventSource(source, callback) {
+ var i;
+ var fetchers = FC.sourceFetchers;
+ var res;
+
+ for (i=0; i<fetchers.length; i++) {
+ res = fetchers[i].call(
+ t, // this, the Calendar object
+ source,
+ rangeStart.clone(),
+ rangeEnd.clone(),
+ options.timezone,
+ callback
+ );
+
+ if (res === true) {
+ // the fetcher is in charge. made its own async request
+ return;
+ }
+ else if (typeof res == 'object') {
+ // the fetcher returned a new source. process it
+ _fetchEventSource(res, callback);
+ return;
+ }
+ }
+
+ var events = source.events;
+ if (events) {
+ if ($.isFunction(events)) {
+ t.pushLoading();
+ events.call(
+ t, // this, the Calendar object
+ rangeStart.clone(),
+ rangeEnd.clone(),
+ options.timezone,
+ function(events) {
+ callback(events);
+ t.popLoading();
+ }
+ );
+ }
+ else if ($.isArray(events)) {
+ callback(events);
+ }
+ else {
+ callback();
+ }
+ }else{
+ var url = source.url;
+ if (url) {
+ var success = source.success;
+ var error = source.error;
+ var complete = source.complete;
+
+ // retrieve any outbound GET/POST $.ajax data from the options
+ var customData;
+ if ($.isFunction(source.data)) {
+ // supplied as a function that returns a key/value object
+ customData = source.data();
+ }
+ else {
+ // supplied as a straight key/value object
+ customData = source.data;
+ }
+
+ // use a copy of the custom data so we can modify the parameters
+ // and not affect the passed-in object.
+ var data = $.extend({}, customData || {});
+
+ var startParam = firstDefined(source.startParam, options.startParam);
+ var endParam = firstDefined(source.endParam, options.endParam);
+ var timezoneParam = firstDefined(source.timezoneParam, options.timezoneParam);
+
+ if (startParam) {
+ data[startParam] = rangeStart.format();
+ }
+ if (endParam) {
+ data[endParam] = rangeEnd.format();
+ }
+ if (options.timezone && options.timezone != 'local') {
+ data[timezoneParam] = options.timezone;
+ }
+
+ t.pushLoading();
+ $.ajax($.extend({}, ajaxDefaults, source, {
+ data: data,
+ success: function(events) {
+ events = events || [];
+ var res = applyAll(success, this, arguments);
+ if ($.isArray(res)) {
+ events = res;
+ }
+ callback(events);
+ },
+ error: function() {
+ applyAll(error, this, arguments);
+ callback();
+ },
+ complete: function() {
+ applyAll(complete, this, arguments);
+ t.popLoading();
+ }
+ }));
+ }else{
+ callback();
+ }
+ }
+ }
+
+
+
+ /* Sources
+ -----------------------------------------------------------------------------*/
+
+
+ function addEventSource(sourceInput) {
+ var source = buildEventSource(sourceInput);
+ if (source) {
+ sources.push(source);
+ pendingSourceCnt++;
+ fetchEventSource(source, currentFetchID); // will eventually call reportEvents
+ }
+ }
+
+
+ function buildEventSource(sourceInput) { // will return undefined if invalid source
+ var normalizers = FC.sourceNormalizers;
+ var source;
+ var i;
+
+ if ($.isFunction(sourceInput) || $.isArray(sourceInput)) {
+ source = { events: sourceInput };
+ }
+ else if (typeof sourceInput === 'string') {
+ source = { url: sourceInput };
+ }
+ else if (typeof sourceInput === 'object') {
+ source = $.extend({}, sourceInput); // shallow copy
+ }
+
+ if (source) {
+
+ // TODO: repeat code, same code for event classNames
+ if (source.className) {
+ if (typeof source.className === 'string') {
+ source.className = source.className.split(/\s+/);
+ }
+ // otherwise, assumed to be an array
+ }
+ else {
+ source.className = [];
+ }
+
+ // for array sources, we convert to standard Event Objects up front
+ if ($.isArray(source.events)) {
+ source.origArray = source.events; // for removeEventSource
+ source.events = $.map(source.events, function(eventInput) {
+ return buildEventFromInput(eventInput, source);
+ });
+ }
+
+ for (i=0; i<normalizers.length; i++) {
+ normalizers[i].call(t, source);
+ }
+
+ return source;
+ }
+ }
+
+
+ function removeEventSource(source) {
+ sources = $.grep(sources, function(src) {
+ return !isSourcesEqual(src, source);
+ });
+ // remove all client events from that source
+ cache = $.grep(cache, function(e) {
+ return !isSourcesEqual(e.source, source);
+ });
+ reportEvents(cache);
+ }
+
+
+ function isSourcesEqual(source1, source2) {
+ return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
+ }
+
+
+ function getSourcePrimitive(source) {
+ return (
+ (typeof source === 'object') ? // a normalized event source?
+ (source.origArray || source.googleCalendarId || source.url || source.events) : // get the primitive
+ null
+ ) ||
+ source; // the given argument *is* the primitive
+ }
+
+
+
+ /* Manipulation
+ -----------------------------------------------------------------------------*/
+
+
+ // Only ever called from the externally-facing API
+ function updateEvent(event) {
+
+ // massage start/end values, even if date string values
+ event.start = t.moment(event.start);
+ if (event.end) {
+ event.end = t.moment(event.end);
+ }
+ else {
+ event.end = null;
+ }
+
+ mutateEvent(event, getMiscEventProps(event)); // will handle start/end/allDay normalization
+ reportEvents(cache); // reports event modifications (so we can redraw)
+ }
+
+
+ // Returns a hash of misc event properties that should be copied over to related events.
+ function getMiscEventProps(event) {
+ var props = {};
+
+ $.each(event, function(name, val) {
+ if (isMiscEventPropName(name)) {
+ if (val !== undefined && isAtomic(val)) { // a defined non-object
+ props[name] = val;
+ }
+ }
+ });
+
+ return props;
+ }
+
+ // non-date-related, non-id-related, non-secret
+ function isMiscEventPropName(name) {
+ return !/^_|^(id|allDay|start|end)$/.test(name);
+ }
+
+
+ // returns the expanded events that were created
+ function renderEvent(eventInput, stick) {
+ var abstractEvent = buildEventFromInput(eventInput);
+ var events;
+ var i, event;
+
+ if (abstractEvent) { // not false (a valid input)
+ events = expandEvent(abstractEvent);
+
+ for (i = 0; i < events.length; i++) {
+ event = events[i];
+
+ if (!event.source) {
+ if (stick) {
+ stickySource.events.push(event);
+ event.source = stickySource;
+ }
+ cache.push(event);
+ }
+ }
+
+ reportEvents(cache);
+
+ return events;
+ }
+
+ return [];
+ }
+
+
+ function removeEvents(filter) {
+ var eventID;
+ var i;
+
+ if (filter == null) { // null or undefined. remove all events
+ filter = function() { return true; }; // will always match
+ }
+ else if (!$.isFunction(filter)) { // an event ID
+ eventID = filter + '';
+ filter = function(event) {
+ return event._id == eventID;
+ };
+ }
+
+ // Purge event(s) from our local cache
+ cache = $.grep(cache, filter, true); // inverse=true
+
+ // Remove events from array sources.
+ // This works because they have been converted to official Event Objects up front.
+ // (and as a result, event._id has been calculated).
+ for (i=0; i<sources.length; i++) {
+ if ($.isArray(sources[i].events)) {
+ sources[i].events = $.grep(sources[i].events, filter, true);
+ }
+ }
+
+ reportEvents(cache);
+ }
+
+
+ function clientEvents(filter) {
+ if ($.isFunction(filter)) {
+ return $.grep(cache, filter);
+ }
+ else if (filter != null) { // not null, not undefined. an event ID
+ filter += '';
+ return $.grep(cache, function(e) {
+ return e._id == filter;
+ });
+ }
+ return cache; // else, return all
+ }
+
+
+
+ /* Event Normalization
+ -----------------------------------------------------------------------------*/
+
+
+ // Given a raw object with key/value properties, returns an "abstract" Event object.
+ // An "abstract" event is an event that, if recurring, will not have been expanded yet.
+ // Will return `false` when input is invalid.
+ // `source` is optional
+ function buildEventFromInput(input, source) {
+ var out = {};
+ var start, end;
+ var allDay;
+
+ if (options.eventDataTransform) {
+ input = options.eventDataTransform(input);
+ }
+ if (source && source.eventDataTransform) {
+ input = source.eventDataTransform(input);
+ }
+
+ // Copy all properties over to the resulting object.
+ // The special-case properties will be copied over afterwards.
+ $.extend(out, input);
+
+ if (source) {
+ out.source = source;
+ }
+
+ out._id = input._id || (input.id === undefined ? '_fc' + eventGUID++ : input.id + '');
+
+ if (input.className) {
+ if (typeof input.className == 'string') {
+ out.className = input.className.split(/\s+/);
+ }
+ else { // assumed to be an array
+ out.className = input.className;
+ }
+ }
+ else {
+ out.className = [];
+ }
+
+ start = input.start || input.date; // "date" is an alias for "start"
+ end = input.end;
+
+ // parse as a time (Duration) if applicable
+ if (isTimeString(start)) {
+ start = moment.duration(start);
+ }
+ if (isTimeString(end)) {
+ end = moment.duration(end);
+ }
+
+ if (input.dow || moment.isDuration(start) || moment.isDuration(end)) {
+
+ // the event is "abstract" (recurring) so don't calculate exact start/end dates just yet
+ out.start = start ? moment.duration(start) : null; // will be a Duration or null
+ out.end = end ? moment.duration(end) : null; // will be a Duration or null
+ out._recurring = true; // our internal marker
+ }
+ else {
+
+ if (start) {
+ start = t.moment(start);
+ if (!start.isValid()) {
+ return false;
+ }
+ }
+
+ if (end) {
+ end = t.moment(end);
+ if (!end.isValid()) {
+ end = null; // let defaults take over
+ }
+ }
+
+ allDay = input.allDay;
+ if (allDay === undefined) { // still undefined? fallback to default
+ allDay = firstDefined(
+ source ? source.allDayDefault : undefined,
+ options.allDayDefault
+ );
+ // still undefined? normalizeEventDates will calculate it
+ }
+
+ assignDatesToEvent(start, end, allDay, out);
+ }
+
+ return out;
+ }
+
+
+ // Normalizes and assigns the given dates to the given partially-formed event object.
+ // NOTE: mutates the given start/end moments. does not make a copy.
+ function assignDatesToEvent(start, end, allDay, event) {
+ event.start = start;
+ event.end = end;
+ event.allDay = allDay;
+ normalizeEventDates(event);
+ backupEventDates(event);
+ }
+
+
+ // Ensures proper values for allDay/start/end. Accepts an Event object, or a plain object with event-ish properties.
+ // NOTE: Will modify the given object.
+ function normalizeEventDates(eventProps) {
+
+ normalizeEventTimes(eventProps);
+
+ if (eventProps.end && !eventProps.end.isAfter(eventProps.start)) {
+ eventProps.end = null;
+ }
+
+ if (!eventProps.end) {
+ if (options.forceEventDuration) {
+ eventProps.end = t.getDefaultEventEnd(eventProps.allDay, eventProps.start);
+ }
+ else {
+ eventProps.end = null;
+ }
+ }
+ }
+
+
+ // Ensures the allDay property exists and the timeliness of the start/end dates are consistent
+ function normalizeEventTimes(eventProps) {
+ if (eventProps.allDay == null) {
+ eventProps.allDay = !(eventProps.start.hasTime() || (eventProps.end && eventProps.end.hasTime()));
+ }
+
+ if (eventProps.allDay) {
+ eventProps.start.stripTime();
+ if (eventProps.end) {
+ // TODO: consider nextDayThreshold here? If so, will require a lot of testing and adjustment
+ eventProps.end.stripTime();
+ }
+ }
+ else {
+ if (!eventProps.start.hasTime()) {
+ eventProps.start = t.applyTimezone(eventProps.start.time(0)); // will assign a 00:00 time
+ }
+ if (eventProps.end && !eventProps.end.hasTime()) {
+ eventProps.end = t.applyTimezone(eventProps.end.time(0)); // will assign a 00:00 time
+ }
+ }
+ }
+
+
+ // If the given event is a recurring event, break it down into an array of individual instances.
+ // If not a recurring event, return an array with the single original event.
+ // If given a falsy input (probably because of a failed buildEventFromInput call), returns an empty array.
+ // HACK: can override the recurring window by providing custom rangeStart/rangeEnd (for businessHours).
+ function expandEvent(abstractEvent, _rangeStart, _rangeEnd) {
+ var events = [];
+ var dowHash;
+ var dow;
+ var i;
+ var date;
+ var startTime, endTime;
+ var start, end;
+ var event;
+
+ _rangeStart = _rangeStart || rangeStart;
+ _rangeEnd = _rangeEnd || rangeEnd;
+
+ if (abstractEvent) {
+ if (abstractEvent._recurring) {
+
+ // make a boolean hash as to whether the event occurs on each day-of-week
+ if ((dow = abstractEvent.dow)) {
+ dowHash = {};
+ for (i = 0; i < dow.length; i++) {
+ dowHash[dow[i]] = true;
+ }
+ }
+
+ // iterate through every day in the current range
+ date = _rangeStart.clone().stripTime(); // holds the date of the current day
+ while (date.isBefore(_rangeEnd)) {
+
+ if (!dowHash || dowHash[date.day()]) { // if everyday, or this particular day-of-week
+
+ startTime = abstractEvent.start; // the stored start and end properties are times (Durations)
+ endTime = abstractEvent.end; // "
+ start = date.clone();
+ end = null;
+
+ if (startTime) {
+ start = start.time(startTime);
+ }
+ if (endTime) {
+ end = date.clone().time(endTime);
+ }
+
+ event = $.extend({}, abstractEvent); // make a copy of the original
+ assignDatesToEvent(
+ start, end,
+ !startTime && !endTime, // allDay?
+ event
+ );
+ events.push(event);
+ }
+
+ date.add(1, 'days');
+ }
+ }
+ else {
+ events.push(abstractEvent); // return the original event. will be a one-item array
+ }
+ }
+
+ return events;
+ }
+
+
+
+ /* Event Modification Math
+ -----------------------------------------------------------------------------------------*/
+
+
+ // Modifies an event and all related events by applying the given properties.
+ // Special date-diffing logic is used for manipulation of dates.
+ // If `props` does not contain start/end dates, the updated values are assumed to be the event's current start/end.
+ // All date comparisons are done against the event's pristine _start and _end dates.
+ // Returns an object with delta information and a function to undo all operations.
+ // For making computations in a granularity greater than day/time, specify largeUnit.
+ // NOTE: The given `newProps` might be mutated for normalization purposes.
+ function mutateEvent(event, newProps, largeUnit) {
+ var miscProps = {};
+ var oldProps;
+ var clearEnd;
+ var startDelta;
+ var endDelta;
+ var durationDelta;
+ var undoFunc;
+
+ // diffs the dates in the appropriate way, returning a duration
+ function diffDates(date1, date0) { // date1 - date0
+ if (largeUnit) {
+ return diffByUnit(date1, date0, largeUnit);
+ }
+ else if (newProps.allDay) {
+ return diffDay(date1, date0);
+ }
+ else {
+ return diffDayTime(date1, date0);
+ }
+ }
+
+ newProps = newProps || {};
+
+ // normalize new date-related properties
+ if (!newProps.start) {
+ newProps.start = event.start.clone();
+ }
+ if (newProps.end === undefined) {
+ newProps.end = event.end ? event.end.clone() : null;
+ }
+ if (newProps.allDay == null) { // is null or undefined?
+ newProps.allDay = event.allDay;
+ }
+ normalizeEventDates(newProps);
+
+ // create normalized versions of the original props to compare against
+ // need a real end value, for diffing
+ oldProps = {
+ start: event._start.clone(),
+ end: event._end ? event._end.clone() : t.getDefaultEventEnd(event._allDay, event._start),
+ allDay: newProps.allDay // normalize the dates in the same regard as the new properties
+ };
+ normalizeEventDates(oldProps);
+
+ // need to clear the end date if explicitly changed to null
+ clearEnd = event._end !== null && newProps.end === null;
+
+ // compute the delta for moving the start date
+ startDelta = diffDates(newProps.start, oldProps.start);
+
+ // compute the delta for moving the end date
+ if (newProps.end) {
+ endDelta = diffDates(newProps.end, oldProps.end);
+ durationDelta = endDelta.subtract(startDelta);
+ }
+ else {
+ durationDelta = null;
+ }
+
+ // gather all non-date-related properties
+ $.each(newProps, function(name, val) {
+ if (isMiscEventPropName(name)) {
+ if (val !== undefined) {
+ miscProps[name] = val;
+ }
+ }
+ });
+
+ // apply the operations to the event and all related events
+ undoFunc = mutateEvents(
+ clientEvents(event._id), // get events with this ID
+ clearEnd,
+ newProps.allDay,
+ startDelta,
+ durationDelta,
+ miscProps
+ );
+
+ return {
+ dateDelta: startDelta,
+ durationDelta: durationDelta,
+ undo: undoFunc
+ };
+ }
+
+
+ // Modifies an array of events in the following ways (operations are in order):
+ // - clear the event's `end`
+ // - convert the event to allDay
+ // - add `dateDelta` to the start and end
+ // - add `durationDelta` to the event's duration
+ // - assign `miscProps` to the event
+ //
+ // Returns a function that can be called to undo all the operations.
+ //
+ // TODO: don't use so many closures. possible memory issues when lots of events with same ID.
+ //
+ function mutateEvents(events, clearEnd, allDay, dateDelta, durationDelta, miscProps) {
+ var isAmbigTimezone = t.getIsAmbigTimezone();
+ var undoFunctions = [];
+
+ // normalize zero-length deltas to be null
+ if (dateDelta && !dateDelta.valueOf()) { dateDelta = null; }
+ if (durationDelta && !durationDelta.valueOf()) { durationDelta = null; }
+
+ $.each(events, function(i, event) {
+ var oldProps;
+ var newProps;
+
+ // build an object holding all the old values, both date-related and misc.
+ // for the undo function.
+ oldProps = {
+ start: event.start.clone(),
+ end: event.end ? event.end.clone() : null,
+ allDay: event.allDay
+ };
+ $.each(miscProps, function(name) {
+ oldProps[name] = event[name];
+ });
+
+ // new date-related properties. work off the original date snapshot.
+ // ok to use references because they will be thrown away when backupEventDates is called.
+ newProps = {
+ start: event._start,
+ end: event._end,
+ allDay: allDay // normalize the dates in the same regard as the new properties
+ };
+ normalizeEventDates(newProps); // massages start/end/allDay
+
+ // strip or ensure the end date
+ if (clearEnd) {
+ newProps.end = null;
+ }
+ else if (durationDelta && !newProps.end) { // the duration translation requires an end date
+ newProps.end = t.getDefaultEventEnd(newProps.allDay, newProps.start);
+ }
+
+ if (dateDelta) {
+ newProps.start.add(dateDelta);
+ if (newProps.end) {
+ newProps.end.add(dateDelta);
+ }
+ }
+
+ if (durationDelta) {
+ newProps.end.add(durationDelta); // end already ensured above
+ }
+
+ // if the dates have changed, and we know it is impossible to recompute the
+ // timezone offsets, strip the zone.
+ if (
+ isAmbigTimezone &&
+ !newProps.allDay &&
+ (dateDelta || durationDelta)
+ ) {
+ newProps.start.stripZone();
+ if (newProps.end) {
+ newProps.end.stripZone();
+ }
+ }
+
+ $.extend(event, miscProps, newProps); // copy over misc props, then date-related props
+ backupEventDates(event); // regenerate internal _start/_end/_allDay
+
+ undoFunctions.push(function() {
+ $.extend(event, oldProps);
+ backupEventDates(event); // regenerate internal _start/_end/_allDay
+ });
+ });
+
+ return function() {
+ for (var i = 0; i < undoFunctions.length; i++) {
+ undoFunctions[i]();
+ }
+ };
+ }
+
+
+ /* Business Hours
+ -----------------------------------------------------------------------------------------*/
+
+ t.getBusinessHoursEvents = getBusinessHoursEvents;
+
+
+ // Returns an array of events as to when the business hours occur in the given view.
+ // Abuse of our event system :(
+ function getBusinessHoursEvents(wholeDay) {
+ var optionVal = options.businessHours;
+ var defaultVal = {
+ className: 'fc-nonbusiness',
+ start: '09:00',
+ end: '17:00',
+ dow: [ 1, 2, 3, 4, 5 ], // monday - friday
+ rendering: 'inverse-background'
+ };
+ var view = t.getView();
+ var eventInput;
+
+ if (optionVal) { // `true` (which means "use the defaults") or an override object
+ eventInput = $.extend(
+ {}, // copy to a new object in either case
+ defaultVal,
+ typeof optionVal === 'object' ? optionVal : {} // override the defaults
+ );
+ }
+
+ if (eventInput) {
+
+ // if a whole-day series is requested, clear the start/end times
+ if (wholeDay) {
+ eventInput.start = null;
+ eventInput.end = null;
+ }
+
+ return expandEvent(
+ buildEventFromInput(eventInput),
+ view.start,
+ view.end
+ );
+ }
+
+ return [];
+ }
+
+
+ /* Overlapping / Constraining
+ -----------------------------------------------------------------------------------------*/
+
+ t.isEventSpanAllowed = isEventSpanAllowed;
+ t.isExternalSpanAllowed = isExternalSpanAllowed;
+ t.isSelectionSpanAllowed = isSelectionSpanAllowed;
+
+
+ // Determines if the given event can be relocated to the given span (unzoned start/end with other misc data)
+ function isEventSpanAllowed(span, event) {
+ var source = event.source || {};
+ var constraint = firstDefined(
+ event.constraint,
+ source.constraint,
+ options.eventConstraint
+ );
+ var overlap = firstDefined(
+ event.overlap,
+ source.overlap,
+ options.eventOverlap
+ );
+ return isSpanAllowed(span, constraint, overlap, event);
+ }
+
+
+ // Determines if an external event can be relocated to the given span (unzoned start/end with other misc data)
+ function isExternalSpanAllowed(eventSpan, eventLocation, eventProps) {
+ var eventInput;
+ var event;
+
+ // note: very similar logic is in View's reportExternalDrop
+ if (eventProps) {
+ eventInput = $.extend({}, eventProps, eventLocation);
+ event = expandEvent(buildEventFromInput(eventInput))[0];
+ }
+
+ if (event) {
+ return isEventSpanAllowed(eventSpan, event);
+ }
+ else { // treat it as a selection
+
+ return isSelectionSpanAllowed(eventSpan);
+ }
+ }
+
+
+ // Determines the given span (unzoned start/end with other misc data) can be selected.
+ function isSelectionSpanAllowed(span) {
+ return isSpanAllowed(span, options.selectConstraint, options.selectOverlap);
+ }
+
+
+ // Returns true if the given span (caused by an event drop/resize or a selection) is allowed to exist
+ // according to the constraint/overlap settings.
+ // `event` is not required if checking a selection.
+ function isSpanAllowed(span, constraint, overlap, event) {
+ var constraintEvents;
+ var anyContainment;
+ var peerEvents;
+ var i, peerEvent;
+ var peerOverlap;
+
+ // the range must be fully contained by at least one of produced constraint events
+ if (constraint != null) {
+
+ // not treated as an event! intermediate data structure
+ // TODO: use ranges in the future
+ constraintEvents = constraintToEvents(constraint);
+
+ anyContainment = false;
+ for (i = 0; i < constraintEvents.length; i++) {
+ if (eventContainsRange(constraintEvents[i], span)) {
+ anyContainment = true;
+ break;
+ }
+ }
+
+ if (!anyContainment) {
+ return false;
+ }
+ }
+
+ peerEvents = t.getPeerEvents(span, event);
+
+ for (i = 0; i < peerEvents.length; i++) {
+ peerEvent = peerEvents[i];
+
+ // there needs to be an actual intersection before disallowing anything
+ if (eventIntersectsRange(peerEvent, span)) {
+
+ // evaluate overlap for the given range and short-circuit if necessary
+ if (overlap === false) {
+ return false;
+ }
+ // if the event's overlap is a test function, pass the peer event in question as the first param
+ else if (typeof overlap === 'function' && !overlap(peerEvent, event)) {
+ return false;
+ }
+
+ // if we are computing if the given range is allowable for an event, consider the other event's
+ // EventObject-specific or Source-specific `overlap` property
+ if (event) {
+ peerOverlap = firstDefined(
+ peerEvent.overlap,
+ (peerEvent.source || {}).overlap
+ // we already considered the global `eventOverlap`
+ );
+ if (peerOverlap === false) {
+ return false;
+ }
+ // if the peer event's overlap is a test function, pass the subject event as the first param
+ if (typeof peerOverlap === 'function' && !peerOverlap(event, peerEvent)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+ // Given an event input from the API, produces an array of event objects. Possible event inputs:
+ // 'businessHours'
+ // An event ID (number or string)
+ // An object with specific start/end dates or a recurring event (like what businessHours accepts)
+ function constraintToEvents(constraintInput) {
+
+ if (constraintInput === 'businessHours') {
+ return getBusinessHoursEvents();
+ }
+
+ if (typeof constraintInput === 'object') {
+ return expandEvent(buildEventFromInput(constraintInput));
+ }
+
+ return clientEvents(constraintInput); // probably an ID
+ }
+
+
+ // Does the event's date range fully contain the given range?
+ // start/end already assumed to have stripped zones :(
+ function eventContainsRange(event, range) {
+ var eventStart = event.start.clone().stripZone();
+ var eventEnd = t.getEventEnd(event).stripZone();
+
+ return range.start >= eventStart && range.end <= eventEnd;
+ }
+
+
+ // Does the event's date range intersect with the given range?
+ // start/end already assumed to have stripped zones :(
+ function eventIntersectsRange(event, range) {
+ var eventStart = event.start.clone().stripZone();
+ var eventEnd = t.getEventEnd(event).stripZone();
+
+ return range.start < eventEnd && range.end > eventStart;
+ }
+
+
+ t.getEventCache = function() {
+ return cache;
+ };
+
+}
+
+
+// Returns a list of events that the given event should be compared against when being considered for a move to
+// the specified span. Attached to the Calendar's prototype because EventManager is a mixin for a Calendar.
+Calendar.prototype.getPeerEvents = function(span, event) {
+ var cache = this.getEventCache();
+ var peerEvents = [];
+ var i, otherEvent;
+
+ for (i = 0; i < cache.length; i++) {
+ otherEvent = cache[i];
+ if (
+ !event ||
+ event._id !== otherEvent._id // don't compare the event to itself or other related [repeating] events
+ ) {
+ peerEvents.push(otherEvent);
+ }
+ }
+
+ return peerEvents;
+};
+
+
+// updates the "backup" properties, which are preserved in order to compute diffs later on.
+function backupEventDates(event) {
+ event._allDay = event.allDay;
+ event._start = event.start.clone();
+ event._end = event.end ? event.end.clone() : null;
+}
+
+;;
+
+/* An abstract class for the "basic" views, as well as month view. Renders one or more rows of day cells.
+----------------------------------------------------------------------------------------------------------------------*/
+// It is a manager for a DayGrid subcomponent, which does most of the heavy lifting.
+// It is responsible for managing width/height.
+
+var BasicView = FC.BasicView = View.extend({
+
+ scroller: null,
+
+ dayGridClass: DayGrid, // class the dayGrid will be instantiated from (overridable by subclasses)
+ dayGrid: null, // the main subcomponent that does most of the heavy lifting
+
+ dayNumbersVisible: false, // display day numbers on each day cell?
+ weekNumbersVisible: false, // display week numbers along the side?
+
+ weekNumberWidth: null, // width of all the week-number cells running down the side
+
+ headContainerEl: null, // div that hold's the dayGrid's rendered date header
+ headRowEl: null, // the fake row element of the day-of-week header
+
+
+ initialize: function() {
+ this.dayGrid = this.instantiateDayGrid();
+
+ this.scroller = new Scroller({
+ overflowX: 'hidden',
+ overflowY: 'auto'
+ });
+ },
+
+
+ // Generates the DayGrid object this view needs. Draws from this.dayGridClass
+ instantiateDayGrid: function() {
+ // generate a subclass on the fly with BasicView-specific behavior
+ // TODO: cache this subclass
+ var subclass = this.dayGridClass.extend(basicDayGridMethods);
+
+ return new subclass(this);
+ },
+
+
+ // Sets the display range and computes all necessary dates
+ setRange: function(range) {
+ View.prototype.setRange.call(this, range); // call the super-method
+
+ this.dayGrid.breakOnWeeks = /year|month|week/.test(this.intervalUnit); // do before setRange
+ this.dayGrid.setRange(range);
+ },
+
+
+ // Compute the value to feed into setRange. Overrides superclass.
+ computeRange: function(date) {
+ var range = View.prototype.computeRange.call(this, date); // get value from the super-method
+
+ // year and month views should be aligned with weeks. this is already done for week
+ if (/year|month/.test(range.intervalUnit)) {
+ range.start.startOf('week');
+ range.start = this.skipHiddenDays(range.start);
+
+ // make end-of-week if not already
+ if (range.end.weekday()) {
+ range.end.add(1, 'week').startOf('week');
+ range.end = this.skipHiddenDays(range.end, -1, true); // exclusively move backwards
+ }
+ }
+
+ return range;
+ },
+
+
+ // Renders the view into `this.el`, which should already be assigned
+ renderDates: function() {
+
+ this.dayNumbersVisible = this.dayGrid.rowCnt > 1; // TODO: make grid responsible
+ this.weekNumbersVisible = this.opt('weekNumbers');
+ this.dayGrid.numbersVisible = this.dayNumbersVisible || this.weekNumbersVisible;
+
+ this.el.addClass('fc-basic-view').html(this.renderSkeletonHtml());
+ this.renderHead();
+
+ this.scroller.render();
+ var dayGridContainerEl = this.scroller.el.addClass('fc-day-grid-container');
+ var dayGridEl = $('<div class="fc-day-grid" />').appendTo(dayGridContainerEl);
+ this.el.find('.fc-body > tr > td').append(dayGridContainerEl);
+
+ this.dayGrid.setElement(dayGridEl);
+ this.dayGrid.renderDates(this.hasRigidRows());
+ },
+
+
+ // render the day-of-week headers
+ renderHead: function() {
+ this.headContainerEl =
+ this.el.find('.fc-head-container')
+ .html(this.dayGrid.renderHeadHtml());
+ this.headRowEl = this.headContainerEl.find('.fc-row');
+ },
+
+
+ // Unrenders the content of the view. Since we haven't separated skeleton rendering from date rendering,
+ // always completely kill the dayGrid's rendering.
+ unrenderDates: function() {
+ this.dayGrid.unrenderDates();
+ this.dayGrid.removeElement();
+ this.scroller.destroy();
+ },
+
+
+ renderBusinessHours: function() {
+ this.dayGrid.renderBusinessHours();
+ },
+
+
+ // Builds the HTML skeleton for the view.
+ // The day-grid component will render inside of a container defined by this HTML.
+ renderSkeletonHtml: function() {
+ return '' +
+ '<table>' +
+ '<thead class="fc-head">' +
+ '<tr>' +
+ '<td class="fc-head-container ' + this.widgetHeaderClass + '"></td>' +
+ '</tr>' +
+ '</thead>' +
+ '<tbody class="fc-body">' +
+ '<tr>' +
+ '<td class="' + this.widgetContentClass + '"></td>' +
+ '</tr>' +
+ '</tbody>' +
+ '</table>';
+ },
+
+
+ // Generates an HTML attribute string for setting the width of the week number column, if it is known
+ weekNumberStyleAttr: function() {
+ if (this.weekNumberWidth !== null) {
+ return 'style="width:' + this.weekNumberWidth + 'px"';
+ }
+ return '';
+ },
+
+
+ // Determines whether each row should have a constant height
+ hasRigidRows: function() {
+ var eventLimit = this.opt('eventLimit');
+ return eventLimit && typeof eventLimit !== 'number';
+ },
+
+
+ /* Dimensions
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Refreshes the horizontal dimensions of the view
+ updateWidth: function() {
+ if (this.weekNumbersVisible) {
+ // Make sure all week number cells running down the side have the same width.
+ // Record the width for cells created later.
+ this.weekNumberWidth = matchCellWidths(
+ this.el.find('.fc-week-number')
+ );
+ }
+ },
+
+
+ // Adjusts the vertical dimensions of the view to the specified values
+ setHeight: function(totalHeight, isAuto) {
+ var eventLimit = this.opt('eventLimit');
+ var scrollerHeight;
+ var scrollbarWidths;
+
+ // reset all heights to be natural
+ this.scroller.clear();
+ uncompensateScroll(this.headRowEl);
+
+ this.dayGrid.removeSegPopover(); // kill the "more" popover if displayed
+
+ // is the event limit a constant level number?
+ if (eventLimit && typeof eventLimit === 'number') {
+ this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after
+ }
+
+ // distribute the height to the rows
+ // (totalHeight is a "recommended" value if isAuto)
+ scrollerHeight = this.computeScrollerHeight(totalHeight);
+ this.setGridHeight(scrollerHeight, isAuto);
+
+ // is the event limit dynamically calculated?
+ if (eventLimit && typeof eventLimit !== 'number') {
+ this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set
+ }
+
+ if (!isAuto) { // should we force dimensions of the scroll container?
+
+ this.scroller.setHeight(scrollerHeight);
+ scrollbarWidths = this.scroller.getScrollbarWidths();
+
+ if (scrollbarWidths.left || scrollbarWidths.right) { // using scrollbars?
+
+ compensateScroll(this.headRowEl, scrollbarWidths);
+
+ // doing the scrollbar compensation might have created text overflow which created more height. redo
+ scrollerHeight = this.computeScrollerHeight(totalHeight);
+ this.scroller.setHeight(scrollerHeight);
+ }
+
+ // guarantees the same scrollbar widths
+ this.scroller.lockOverflow(scrollbarWidths);
+ }
+ },
+
+
+ // given a desired total height of the view, returns what the height of the scroller should be
+ computeScrollerHeight: function(totalHeight) {
+ return totalHeight -
+ subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller
+ },
+
+
+ // Sets the height of just the DayGrid component in this view
+ setGridHeight: function(height, isAuto) {
+ if (isAuto) {
+ undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding
+ }
+ else {
+ distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows
+ }
+ },
+
+
+ /* Scroll
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ queryScroll: function() {
+ return this.scroller.getScrollTop();
+ },
+
+
+ setScroll: function(top) {
+ this.scroller.setScrollTop(top);
+ },
+
+
+ /* Hit Areas
+ ------------------------------------------------------------------------------------------------------------------*/
+ // forward all hit-related method calls to dayGrid
+
+
+ prepareHits: function() {
+ this.dayGrid.prepareHits();
+ },
+
+
+ releaseHits: function() {
+ this.dayGrid.releaseHits();
+ },
+
+
+ queryHit: function(left, top) {
+ return this.dayGrid.queryHit(left, top);
+ },
+
+
+ getHitSpan: function(hit) {
+ return this.dayGrid.getHitSpan(hit);
+ },
+
+
+ getHitEl: function(hit) {
+ return this.dayGrid.getHitEl(hit);
+ },
+
+
+ /* Events
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders the given events onto the view and populates the segments array
+ renderEvents: function(events) {
+ this.dayGrid.renderEvents(events);
+
+ this.updateHeight(); // must compensate for events that overflow the row
+ },
+
+
+ // Retrieves all segment objects that are rendered in the view
+ getEventSegs: function() {
+ return this.dayGrid.getEventSegs();
+ },
+
+
+ // Unrenders all event elements and clears internal segment data
+ unrenderEvents: function() {
+ this.dayGrid.unrenderEvents();
+
+ // we DON'T need to call updateHeight() because:
+ // A) a renderEvents() call always happens after this, which will eventually call updateHeight()
+ // B) in IE8, this causes a flash whenever events are rerendered
+ },
+
+
+ /* Dragging (for both events and external elements)
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // A returned value of `true` signals that a mock "helper" event has been rendered.
+ renderDrag: function(dropLocation, seg) {
+ return this.dayGrid.renderDrag(dropLocation, seg);
+ },
+
+
+ unrenderDrag: function() {
+ this.dayGrid.unrenderDrag();
+ },
+
+
+ /* Selection
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of a selection
+ renderSelection: function(span) {
+ this.dayGrid.renderSelection(span);
+ },
+
+
+ // Unrenders a visual indications of a selection
+ unrenderSelection: function() {
+ this.dayGrid.unrenderSelection();
+ }
+
+});
+
+
+// Methods that will customize the rendering behavior of the BasicView's dayGrid
+var basicDayGridMethods = {
+
+
+ // Generates the HTML that will go before the day-of week header cells
+ renderHeadIntroHtml: function() {
+ var view = this.view;
+
+ if (view.weekNumbersVisible) {
+ return '' +
+ '<th class="fc-week-number ' + view.widgetHeaderClass + '" ' + view.weekNumberStyleAttr() + '>' +
+ '<span>' + // needed for matchCellWidths
+ htmlEscape(view.opt('weekNumberTitle')) +
+ '</span>' +
+ '</th>';
+ }
+
+ return '';
+ },
+
+
+ // Generates the HTML that will go before content-skeleton cells that display the day/week numbers
+ renderNumberIntroHtml: function(row) {
+ var view = this.view;
+
+ if (view.weekNumbersVisible) {
+ return '' +
+ '<td class="fc-week-number" ' + view.weekNumberStyleAttr() + '>' +
+ '<span>' + // needed for matchCellWidths
+ this.getCellDate(row, 0).format('w') +
+ '</span>' +
+ '</td>';
+ }
+
+ return '';
+ },
+
+
+ // Generates the HTML that goes before the day bg cells for each day-row
+ renderBgIntroHtml: function() {
+ var view = this.view;
+
+ if (view.weekNumbersVisible) {
+ return '<td class="fc-week-number ' + view.widgetContentClass + '" ' +
+ view.weekNumberStyleAttr() + '></td>';
+ }
+
+ return '';
+ },
+
+
+ // Generates the HTML that goes before every other type of row generated by DayGrid.
+ // Affects helper-skeleton and highlight-skeleton rows.
+ renderIntroHtml: function() {
+ var view = this.view;
+
+ if (view.weekNumbersVisible) {
+ return '<td class="fc-week-number" ' + view.weekNumberStyleAttr() + '></td>';
+ }
+
+ return '';
+ }
+
+};
+
+;;
+
+/* A month view with day cells running in rows (one-per-week) and columns
+----------------------------------------------------------------------------------------------------------------------*/
+
+var MonthView = FC.MonthView = BasicView.extend({
+
+ // Produces information about what range to display
+ computeRange: function(date) {
+ var range = BasicView.prototype.computeRange.call(this, date); // get value from super-method
+ var rowCnt;
+
+ // ensure 6 weeks
+ if (this.isFixedWeeks()) {
+ rowCnt = Math.ceil(range.end.diff(range.start, 'weeks', true)); // could be partial weeks due to hiddenDays
+ range.end.add(6 - rowCnt, 'weeks');
+ }
+
+ return range;
+ },
+
+
+ // Overrides the default BasicView behavior to have special multi-week auto-height logic
+ setGridHeight: function(height, isAuto) {
+
+ isAuto = isAuto || this.opt('weekMode') === 'variable'; // LEGACY: weekMode is deprecated
+
+ // if auto, make the height of each row the height that it would be if there were 6 weeks
+ if (isAuto) {
+ height *= this.rowCnt / 6;
+ }
+
+ distributeHeight(this.dayGrid.rowEls, height, !isAuto); // if auto, don't compensate for height-hogging rows
+ },
+
+
+ isFixedWeeks: function() {
+ var weekMode = this.opt('weekMode'); // LEGACY: weekMode is deprecated
+ if (weekMode) {
+ return weekMode === 'fixed'; // if any other type of weekMode, assume NOT fixed
+ }
+
+ return this.opt('fixedWeekCount');
+ }
+
+});
+
+;;
+
+fcViews.basic = {
+ 'class': BasicView
+};
+
+fcViews.basicDay = {
+ type: 'basic',
+ duration: { days: 1 }
+};
+
+fcViews.basicWeek = {
+ type: 'basic',
+ duration: { weeks: 1 }
+};
+
+fcViews.month = {
+ 'class': MonthView,
+ duration: { months: 1 }, // important for prev/next
+ defaults: {
+ fixedWeekCount: true
+ }
+};
+;;
+
+/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically.
+----------------------------------------------------------------------------------------------------------------------*/
+// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on).
+// Responsible for managing width/height.
+
+var AgendaView = FC.AgendaView = View.extend({
+
+ scroller: null,
+
+ timeGridClass: TimeGrid, // class used to instantiate the timeGrid. subclasses can override
+ timeGrid: null, // the main time-grid subcomponent of this view
+
+ dayGridClass: DayGrid, // class used to instantiate the dayGrid. subclasses can override
+ dayGrid: null, // the "all-day" subcomponent. if all-day is turned off, this will be null
+
+ axisWidth: null, // the width of the time axis running down the side
+
+ headContainerEl: null, // div that hold's the timeGrid's rendered date header
+ noScrollRowEls: null, // set of fake row elements that must compensate when scroller has scrollbars
+
+ // when the time-grid isn't tall enough to occupy the given height, we render an <hr> underneath
+ bottomRuleEl: null,
+
+
+ initialize: function() {
+ this.timeGrid = this.instantiateTimeGrid();
+
+ if (this.opt('allDaySlot')) { // should we display the "all-day" area?
+ this.dayGrid = this.instantiateDayGrid(); // the all-day subcomponent of this view
+ }
+
+ this.scroller = new Scroller({
+ overflowX: 'hidden',
+ overflowY: 'auto'
+ });
+ },
+
+
+ // Instantiates the TimeGrid object this view needs. Draws from this.timeGridClass
+ instantiateTimeGrid: function() {
+ var subclass = this.timeGridClass.extend(agendaTimeGridMethods);
+
+ return new subclass(this);
+ },
+
+
+ // Instantiates the DayGrid object this view might need. Draws from this.dayGridClass
+ instantiateDayGrid: function() {
+ var subclass = this.dayGridClass.extend(agendaDayGridMethods);
+
+ return new subclass(this);
+ },
+
+
+ /* Rendering
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Sets the display range and computes all necessary dates
+ setRange: function(range) {
+ View.prototype.setRange.call(this, range); // call the super-method
+
+ this.timeGrid.setRange(range);
+ if (this.dayGrid) {
+ this.dayGrid.setRange(range);
+ }
+ },
+
+
+ // Renders the view into `this.el`, which has already been assigned
+ renderDates: function() {
+
+ this.el.addClass('fc-agenda-view').html(this.renderSkeletonHtml());
+ this.renderHead();
+
+ this.scroller.render();
+ var timeGridWrapEl = this.scroller.el.addClass('fc-time-grid-container');
+ var timeGridEl = $('<div class="fc-time-grid" />').appendTo(timeGridWrapEl);
+ this.el.find('.fc-body > tr > td').append(timeGridWrapEl);
+
+ this.timeGrid.setElement(timeGridEl);
+ this.timeGrid.renderDates();
+
+ // the <hr> that sometimes displays under the time-grid
+ this.bottomRuleEl = $('<hr class="fc-divider ' + this.widgetHeaderClass + '"/>')
+ .appendTo(this.timeGrid.el); // inject it into the time-grid
+
+ if (this.dayGrid) {
+ this.dayGrid.setElement(this.el.find('.fc-day-grid'));
+ this.dayGrid.renderDates();
+
+ // have the day-grid extend it's coordinate area over the <hr> dividing the two grids
+ this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight();
+ }
+
+ this.noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); // fake rows not within the scroller
+ },
+
+
+ // render the day-of-week headers
+ renderHead: function() {
+ this.headContainerEl =
+ this.el.find('.fc-head-container')
+ .html(this.timeGrid.renderHeadHtml());
+ },
+
+
+ // Unrenders the content of the view. Since we haven't separated skeleton rendering from date rendering,
+ // always completely kill each grid's rendering.
+ unrenderDates: function() {
+ this.timeGrid.unrenderDates();
+ this.timeGrid.removeElement();
+
+ if (this.dayGrid) {
+ this.dayGrid.unrenderDates();
+ this.dayGrid.removeElement();
+ }
+
+ this.scroller.destroy();
+ },
+
+
+ // Builds the HTML skeleton for the view.
+ // The day-grid and time-grid components will render inside containers defined by this HTML.
+ renderSkeletonHtml: function() {
+ return '' +
+ '<table>' +
+ '<thead class="fc-head">' +
+ '<tr>' +
+ '<td class="fc-head-container ' + this.widgetHeaderClass + '"></td>' +
+ '</tr>' +
+ '</thead>' +
+ '<tbody class="fc-body">' +
+ '<tr>' +
+ '<td class="' + this.widgetContentClass + '">' +
+ (this.dayGrid ?
+ '<div class="fc-day-grid"/>' +
+ '<hr class="fc-divider ' + this.widgetHeaderClass + '"/>' :
+ ''
+ ) +
+ '</td>' +
+ '</tr>' +
+ '</tbody>' +
+ '</table>';
+ },
+
+
+ // Generates an HTML attribute string for setting the width of the axis, if it is known
+ axisStyleAttr: function() {
+ if (this.axisWidth !== null) {
+ return 'style="width:' + this.axisWidth + 'px"';
+ }
+ return '';
+ },
+
+
+ /* Business Hours
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ renderBusinessHours: function() {
+ this.timeGrid.renderBusinessHours();
+
+ if (this.dayGrid) {
+ this.dayGrid.renderBusinessHours();
+ }
+ },
+
+
+ unrenderBusinessHours: function() {
+ this.timeGrid.unrenderBusinessHours();
+
+ if (this.dayGrid) {
+ this.dayGrid.unrenderBusinessHours();
+ }
+ },
+
+
+ /* Now Indicator
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ getNowIndicatorUnit: function() {
+ return this.timeGrid.getNowIndicatorUnit();
+ },
+
+
+ renderNowIndicator: function(date) {
+ this.timeGrid.renderNowIndicator(date);
+ },
+
+
+ unrenderNowIndicator: function() {
+ this.timeGrid.unrenderNowIndicator();
+ },
+
+
+ /* Dimensions
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ updateSize: function(isResize) {
+ this.timeGrid.updateSize(isResize);
+
+ View.prototype.updateSize.call(this, isResize); // call the super-method
+ },
+
+
+ // Refreshes the horizontal dimensions of the view
+ updateWidth: function() {
+ // make all axis cells line up, and record the width so newly created axis cells will have it
+ this.axisWidth = matchCellWidths(this.el.find('.fc-axis'));
+ },
+
+
+ // Adjusts the vertical dimensions of the view to the specified values
+ setHeight: function(totalHeight, isAuto) {
+ var eventLimit;
+ var scrollerHeight;
+ var scrollbarWidths;
+
+ // reset all dimensions back to the original state
+ this.bottomRuleEl.hide(); // .show() will be called later if this <hr> is necessary
+ this.scroller.clear(); // sets height to 'auto' and clears overflow
+ uncompensateScroll(this.noScrollRowEls);
+
+ // limit number of events in the all-day area
+ if (this.dayGrid) {
+ this.dayGrid.removeSegPopover(); // kill the "more" popover if displayed
+
+ eventLimit = this.opt('eventLimit');
+ if (eventLimit && typeof eventLimit !== 'number') {
+ eventLimit = AGENDA_ALL_DAY_EVENT_LIMIT; // make sure "auto" goes to a real number
+ }
+ if (eventLimit) {
+ this.dayGrid.limitRows(eventLimit);
+ }
+ }
+
+ if (!isAuto) { // should we force dimensions of the scroll container?
+
+ scrollerHeight = this.computeScrollerHeight(totalHeight);
+ this.scroller.setHeight(scrollerHeight);
+ scrollbarWidths = this.scroller.getScrollbarWidths();
+
+ if (scrollbarWidths.left || scrollbarWidths.right) { // using scrollbars?
+
+ // make the all-day and header rows lines up
+ compensateScroll(this.noScrollRowEls, scrollbarWidths);
+
+ // the scrollbar compensation might have changed text flow, which might affect height, so recalculate
+ // and reapply the desired height to the scroller.
+ scrollerHeight = this.computeScrollerHeight(totalHeight);
+ this.scroller.setHeight(scrollerHeight);
+ }
+
+ // guarantees the same scrollbar widths
+ this.scroller.lockOverflow(scrollbarWidths);
+
+ // if there's any space below the slats, show the horizontal rule.
+ // this won't cause any new overflow, because lockOverflow already called.
+ if (this.timeGrid.getTotalSlatHeight() < scrollerHeight) {
+ this.bottomRuleEl.show();
+ }
+ }
+ },
+
+
+ // given a desired total height of the view, returns what the height of the scroller should be
+ computeScrollerHeight: function(totalHeight) {
+ return totalHeight -
+ subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller
+ },
+
+
+ /* Scroll
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Computes the initial pre-configured scroll state prior to allowing the user to change it
+ computeInitialScroll: function() {
+ var scrollTime = moment.duration(this.opt('scrollTime'));
+ var top = this.timeGrid.computeTimeTop(scrollTime);
+
+ // zoom can give weird floating-point values. rather scroll a little bit further
+ top = Math.ceil(top);
+
+ if (top) {
+ top++; // to overcome top border that slots beyond the first have. looks better
+ }
+
+ return top;
+ },
+
+
+ queryScroll: function() {
+ return this.scroller.getScrollTop();
+ },
+
+
+ setScroll: function(top) {
+ this.scroller.setScrollTop(top);
+ },
+
+
+ /* Hit Areas
+ ------------------------------------------------------------------------------------------------------------------*/
+ // forward all hit-related method calls to the grids (dayGrid might not be defined)
+
+
+ prepareHits: function() {
+ this.timeGrid.prepareHits();
+ if (this.dayGrid) {
+ this.dayGrid.prepareHits();
+ }
+ },
+
+
+ releaseHits: function() {
+ this.timeGrid.releaseHits();
+ if (this.dayGrid) {
+ this.dayGrid.releaseHits();
+ }
+ },
+
+
+ queryHit: function(left, top) {
+ var hit = this.timeGrid.queryHit(left, top);
+
+ if (!hit && this.dayGrid) {
+ hit = this.dayGrid.queryHit(left, top);
+ }
+
+ return hit;
+ },
+
+
+ getHitSpan: function(hit) {
+ // TODO: hit.component is set as a hack to identify where the hit came from
+ return hit.component.getHitSpan(hit);
+ },
+
+
+ getHitEl: function(hit) {
+ // TODO: hit.component is set as a hack to identify where the hit came from
+ return hit.component.getHitEl(hit);
+ },
+
+
+ /* Events
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders events onto the view and populates the View's segment array
+ renderEvents: function(events) {
+ var dayEvents = [];
+ var timedEvents = [];
+ var daySegs = [];
+ var timedSegs;
+ var i;
+
+ // separate the events into all-day and timed
+ for (i = 0; i < events.length; i++) {
+ if (events[i].allDay) {
+ dayEvents.push(events[i]);
+ }
+ else {
+ timedEvents.push(events[i]);
+ }
+ }
+
+ // render the events in the subcomponents
+ timedSegs = this.timeGrid.renderEvents(timedEvents);
+ if (this.dayGrid) {
+ daySegs = this.dayGrid.renderEvents(dayEvents);
+ }
+
+ // the all-day area is flexible and might have a lot of events, so shift the height
+ this.updateHeight();
+ },
+
+
+ // Retrieves all segment objects that are rendered in the view
+ getEventSegs: function() {
+ return this.timeGrid.getEventSegs().concat(
+ this.dayGrid ? this.dayGrid.getEventSegs() : []
+ );
+ },
+
+
+ // Unrenders all event elements and clears internal segment data
+ unrenderEvents: function() {
+
+ // unrender the events in the subcomponents
+ this.timeGrid.unrenderEvents();
+ if (this.dayGrid) {
+ this.dayGrid.unrenderEvents();
+ }
+
+ // we DON'T need to call updateHeight() because:
+ // A) a renderEvents() call always happens after this, which will eventually call updateHeight()
+ // B) in IE8, this causes a flash whenever events are rerendered
+ },
+
+
+ /* Dragging (for events and external elements)
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // A returned value of `true` signals that a mock "helper" event has been rendered.
+ renderDrag: function(dropLocation, seg) {
+ if (dropLocation.start.hasTime()) {
+ return this.timeGrid.renderDrag(dropLocation, seg);
+ }
+ else if (this.dayGrid) {
+ return this.dayGrid.renderDrag(dropLocation, seg);
+ }
+ },
+
+
+ unrenderDrag: function() {
+ this.timeGrid.unrenderDrag();
+ if (this.dayGrid) {
+ this.dayGrid.unrenderDrag();
+ }
+ },
+
+
+ /* Selection
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of a selection
+ renderSelection: function(span) {
+ if (span.start.hasTime() || span.end.hasTime()) {
+ this.timeGrid.renderSelection(span);
+ }
+ else if (this.dayGrid) {
+ this.dayGrid.renderSelection(span);
+ }
+ },
+
+
+ // Unrenders a visual indications of a selection
+ unrenderSelection: function() {
+ this.timeGrid.unrenderSelection();
+ if (this.dayGrid) {
+ this.dayGrid.unrenderSelection();
+ }
+ }
+
+});
+
+
+// Methods that will customize the rendering behavior of the AgendaView's timeGrid
+// TODO: move into TimeGrid
+var agendaTimeGridMethods = {
+
+
+ // Generates the HTML that will go before the day-of week header cells
+ renderHeadIntroHtml: function() {
+ var view = this.view;
+ var weekText;
+
+ if (view.opt('weekNumbers')) {
+ weekText = this.start.format(view.opt('smallWeekFormat'));
+
+ return '' +
+ '<th class="fc-axis fc-week-number ' + view.widgetHeaderClass + '" ' + view.axisStyleAttr() + '>' +
+ '<span>' + // needed for matchCellWidths
+ htmlEscape(weekText) +
+ '</span>' +
+ '</th>';
+ }
+ else {
+ return '<th class="fc-axis ' + view.widgetHeaderClass + '" ' + view.axisStyleAttr() + '></th>';
+ }
+ },
+
+
+ // Generates the HTML that goes before the bg of the TimeGrid slot area. Long vertical column.
+ renderBgIntroHtml: function() {
+ var view = this.view;
+
+ return '<td class="fc-axis ' + view.widgetContentClass + '" ' + view.axisStyleAttr() + '></td>';
+ },
+
+
+ // Generates the HTML that goes before all other types of cells.
+ // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid.
+ renderIntroHtml: function() {
+ var view = this.view;
+
+ return '<td class="fc-axis" ' + view.axisStyleAttr() + '></td>';
+ }
+
+};
+
+
+// Methods that will customize the rendering behavior of the AgendaView's dayGrid
+var agendaDayGridMethods = {
+
+
+ // Generates the HTML that goes before the all-day cells
+ renderBgIntroHtml: function() {
+ var view = this.view;
+
+ return '' +
+ '<td class="fc-axis ' + view.widgetContentClass + '" ' + view.axisStyleAttr() + '>' +
+ '<span>' + // needed for matchCellWidths
+ (view.opt('allDayHtml') || htmlEscape(view.opt('allDayText'))) +
+ '</span>' +
+ '</td>';
+ },
+
+
+ // Generates the HTML that goes before all other types of cells.
+ // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid.
+ renderIntroHtml: function() {
+ var view = this.view;
+
+ return '<td class="fc-axis" ' + view.axisStyleAttr() + '></td>';
+ }
+
+};
+
+;;
+
+var AGENDA_ALL_DAY_EVENT_LIMIT = 5;
+
+// potential nice values for the slot-duration and interval-duration
+// from largest to smallest
+var AGENDA_STOCK_SUB_DURATIONS = [
+ { hours: 1 },
+ { minutes: 30 },
+ { minutes: 15 },
+ { seconds: 30 },
+ { seconds: 15 }
+];
+
+fcViews.agenda = {
+ 'class': AgendaView,
+ defaults: {
+ allDaySlot: true,
+ allDayText: 'all-day',
+ slotDuration: '00:30:00',
+ minTime: '00:00:00',
+ maxTime: '24:00:00',
+ slotEventOverlap: true // a bad name. confused with overlap/constraint system
+ }
+};
+
+fcViews.agendaDay = {
+ type: 'agenda',
+ duration: { days: 1 }
+};
+
+fcViews.agendaWeek = {
+ type: 'agenda',
+ duration: { weeks: 1 }
+};
+;;
+
+return FC; // export for Node/CommonJS
+}); \ No newline at end of file
diff --git a/tools/infra-dashboard/js/fullcalendar.min.js b/tools/infra-dashboard/js/fullcalendar.min.js
new file mode 100644
index 00000000..de0babcf
--- /dev/null
+++ b/tools/infra-dashboard/js/fullcalendar.min.js
@@ -0,0 +1,9 @@
+/*!
+ * FullCalendar v2.7.2
+ * Docs & License: http://fullcalendar.io/
+ * (c) 2016 Adam Shaw
+ */
+!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):"object"==typeof exports?module.exports=a(require("jquery"),require("moment")):a(jQuery,moment)}(function(a,b){function c(a){return W(a,Xa)}function d(b){var c,d={views:b.views||{}};return a.each(b,function(b,e){"views"!=b&&(a.isPlainObject(e)&&!/(time|duration|interval)$/i.test(b)&&-1==a.inArray(b,Xa)?(c=null,a.each(e,function(a,e){/^(month|week|day|default|basic(Week|Day)?|agenda(Week|Day)?)$/.test(a)?(d.views[a]||(d.views[a]={}),d.views[a][b]=e):(c||(c={}),c[a]=e)}),c&&(d[b]=c)):d[b]=e)}),d}function e(a,b){b.left&&a.css({"border-left-width":1,"margin-left":b.left-1}),b.right&&a.css({"border-right-width":1,"margin-right":b.right-1})}function f(a){a.css({"margin-left":"","margin-right":"","border-left-width":"","border-right-width":""})}function g(){a("body").addClass("fc-not-allowed")}function h(){a("body").removeClass("fc-not-allowed")}function i(b,c,d){var e=Math.floor(c/b.length),f=Math.floor(c-e*(b.length-1)),g=[],h=[],i=[],k=0;j(b),b.each(function(c,d){var j=c===b.length-1?f:e,l=a(d).outerHeight(!0);j>l?(g.push(d),h.push(l),i.push(a(d).height())):k+=l}),d&&(c-=k,e=Math.floor(c/g.length),f=Math.floor(c-e*(g.length-1))),a(g).each(function(b,c){var d=b===g.length-1?f:e,j=h[b],k=i[b],l=d-(j-k);d>j&&a(c).height(l)})}function j(a){a.height("")}function k(b){var c=0;return b.find("> span").each(function(b,d){var e=a(d).outerWidth();e>c&&(c=e)}),c++,b.width(c),c}function l(a,b){var c,d=a.add(b);return d.css({position:"relative",left:-1}),c=a.outerHeight()-b.outerHeight(),d.css({position:"",left:""}),c}function m(b){var c=b.css("position"),d=b.parents().filter(function(){var b=a(this);return/(auto|scroll)/.test(b.css("overflow")+b.css("overflow-y")+b.css("overflow-x"))}).eq(0);return"fixed"!==c&&d.length?d:a(b[0].ownerDocument||document)}function n(a,b){var c=a.offset(),d=c.left-(b?b.left:0),e=c.top-(b?b.top:0);return{left:d,right:d+a.outerWidth(),top:e,bottom:e+a.outerHeight()}}function o(a,b){var c=a.offset(),d=q(a),e=c.left+t(a,"border-left-width")+d.left-(b?b.left:0),f=c.top+t(a,"border-top-width")+d.top-(b?b.top:0);return{left:e,right:e+a[0].clientWidth,top:f,bottom:f+a[0].clientHeight}}function p(a,b){var c=a.offset(),d=c.left+t(a,"border-left-width")+t(a,"padding-left")-(b?b.left:0),e=c.top+t(a,"border-top-width")+t(a,"padding-top")-(b?b.top:0);return{left:d,right:d+a.width(),top:e,bottom:e+a.height()}}function q(a){var b=a.innerWidth()-a[0].clientWidth,c={left:0,right:0,top:0,bottom:a.innerHeight()-a[0].clientHeight};return r()&&"rtl"==a.css("direction")?c.left=b:c.right=b,c}function r(){return null===Ya&&(Ya=s()),Ya}function s(){var b=a("<div><div/></div>").css({position:"absolute",top:-1e3,left:0,border:0,padding:0,overflow:"scroll",direction:"rtl"}).appendTo("body"),c=b.children(),d=c.offset().left>b.offset().left;return b.remove(),d}function t(a,b){return parseFloat(a.css(b))||0}function u(a){return 1==a.which&&!a.ctrlKey}function v(a){if(void 0!==a.pageX)return a.pageX;var b=a.originalEvent.touches;return b?b[0].pageX:void 0}function w(a){if(void 0!==a.pageY)return a.pageY;var b=a.originalEvent.touches;return b?b[0].pageY:void 0}function x(a){return/^touch/.test(a.type)}function y(a){a.addClass("fc-unselectable").on("selectstart",z)}function z(a){a.preventDefault()}function A(a){return window.addEventListener?(window.addEventListener("scroll",a,!0),!0):!1}function B(a){return window.removeEventListener?(window.removeEventListener("scroll",a,!0),!0):!1}function C(a,b){var c={left:Math.max(a.left,b.left),right:Math.min(a.right,b.right),top:Math.max(a.top,b.top),bottom:Math.min(a.bottom,b.bottom)};return c.left<c.right&&c.top<c.bottom?c:!1}function D(a,b){return{left:Math.min(Math.max(a.left,b.left),b.right),top:Math.min(Math.max(a.top,b.top),b.bottom)}}function E(a){return{left:(a.left+a.right)/2,top:(a.top+a.bottom)/2}}function F(a,b){return{left:a.left-b.left,top:a.top-b.top}}function G(b){var c,d,e=[],f=[];for("string"==typeof b?f=b.split(/\s*,\s*/):"function"==typeof b?f=[b]:a.isArray(b)&&(f=b),c=0;c<f.length;c++)d=f[c],"string"==typeof d?e.push("-"==d.charAt(0)?{field:d.substring(1),order:-1}:{field:d,order:1}):"function"==typeof d&&e.push({func:d});return e}function H(a,b,c){var d,e;for(d=0;d<c.length;d++)if(e=I(a,b,c[d]))return e;return 0}function I(a,b,c){return c.func?c.func(a,b):J(a[c.field],b[c.field])*(c.order||1)}function J(b,c){return b||c?null==c?-1:null==b?1:"string"===a.type(b)||"string"===a.type(c)?String(b).localeCompare(String(c)):b-c:0}function K(a,b){var c,d,e,f,g=a.start,h=a.end,i=b.start,j=b.end;return h>i&&j>g?(g>=i?(c=g.clone(),e=!0):(c=i.clone(),e=!1),j>=h?(d=h.clone(),f=!0):(d=j.clone(),f=!1),{start:c,end:d,isStart:e,isEnd:f}):void 0}function L(a,c){return b.duration({days:a.clone().stripTime().diff(c.clone().stripTime(),"days"),ms:a.time()-c.time()})}function M(a,c){return b.duration({days:a.clone().stripTime().diff(c.clone().stripTime(),"days")})}function N(a,c,d){return b.duration(Math.round(a.diff(c,d,!0)),d)}function O(a,b){var c,d,e;for(c=0;c<$a.length&&(d=$a[c],e=P(d,a,b),!(e>=1&&ha(e)));c++);return d}function P(a,c,d){return null!=d?d.diff(c,a,!0):b.isDuration(c)?c.as(a):c.end.diff(c.start,a,!0)}function Q(a,b,c){var d;return T(c)?(b-a)/c:(d=c.asMonths(),Math.abs(d)>=1&&ha(d)?b.diff(a,"months",!0)/d:b.diff(a,"days",!0)/c.asDays())}function R(a,b){var c,d;return T(a)||T(b)?a/b:(c=a.asMonths(),d=b.asMonths(),Math.abs(c)>=1&&ha(c)&&Math.abs(d)>=1&&ha(d)?c/d:a.asDays()/b.asDays())}function S(a,c){var d;return T(a)?b.duration(a*c):(d=a.asMonths(),Math.abs(d)>=1&&ha(d)?b.duration({months:d*c}):b.duration({days:a.asDays()*c}))}function T(a){return Boolean(a.hours()||a.minutes()||a.seconds()||a.milliseconds())}function U(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function V(a){return/^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(a)}function W(a,b){var c,d,e,f,g,h,i={};if(b)for(c=0;c<b.length;c++){for(d=b[c],e=[],f=a.length-1;f>=0;f--)if(g=a[f][d],"object"==typeof g)e.unshift(g);else if(void 0!==g){i[d]=g;break}e.length&&(i[d]=W(e))}for(c=a.length-1;c>=0;c--){h=a[c];for(d in h)d in i||(i[d]=h[d])}return i}function X(a){var b=function(){};return b.prototype=a,new b}function Y(a,b){for(var c in a)$(a,c)&&(b[c]=a[c])}function Z(a,b){var c,d,e=["constructor","toString","valueOf"];for(c=0;c<e.length;c++)d=e[c],a[d]!==Object.prototype[d]&&(b[d]=a[d])}function $(a,b){return cb.call(a,b)}function _(b){return/undefined|null|boolean|number|string/.test(a.type(b))}function aa(b,c,d){if(a.isFunction(b)&&(b=[b]),b){var e,f;for(e=0;e<b.length;e++)f=b[e].apply(c,d)||f;return f}}function ba(){for(var a=0;a<arguments.length;a++)if(void 0!==arguments[a])return arguments[a]}function ca(a){return(a+"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&#039;").replace(/"/g,"&quot;").replace(/\n/g,"<br />")}function da(a){return a.replace(/&.*?;/g,"")}function ea(b){var c=[];return a.each(b,function(a,b){null!=b&&c.push(a+":"+b)}),c.join(";")}function fa(a){return a.charAt(0).toUpperCase()+a.slice(1)}function ga(a,b){return a-b}function ha(a){return a%1===0}function ia(a,b){var c=a[b];return function(){return c.apply(a,arguments)}}function ja(a,b,c){var d,e,f,g,h,i=function(){var j=+new Date-g;b>j?d=setTimeout(i,b-j):(d=null,c||(h=a.apply(f,e),f=e=null))};return function(){f=this,e=arguments,g=+new Date;var j=c&&!d;return d||(d=setTimeout(i,b)),j&&(h=a.apply(f,e),f=e=null),h}}function ka(c,d,e){var f,g,h,i,j=c[0],k=1==c.length&&"string"==typeof j;return b.isMoment(j)?(i=b.apply(null,c),ma(j,i)):U(j)||void 0===j?i=b.apply(null,c):(f=!1,g=!1,k?db.test(j)?(j+="-01",c=[j],f=!0,g=!0):(h=eb.exec(j))&&(f=!h[5],g=!0):a.isArray(j)&&(g=!0),i=d||f?b.utc.apply(b,c):b.apply(null,c),f?(i._ambigTime=!0,i._ambigZone=!0):e&&(g?i._ambigZone=!0:k&&(i.utcOffset?i.utcOffset(j):i.zone(j)))),i._fullCalendar=!0,i}function la(a,c){var d,e,f=!1,g=!1,h=a.length,i=[];for(d=0;h>d;d++)e=a[d],b.isMoment(e)||(e=Va.moment.parseZone(e)),f=f||e._ambigTime,g=g||e._ambigZone,i.push(e);for(d=0;h>d;d++)e=i[d],c||!f||e._ambigTime?g&&!e._ambigZone&&(i[d]=e.clone().stripZone()):i[d]=e.clone().stripTime();return i}function ma(a,b){a._ambigTime?b._ambigTime=!0:b._ambigTime&&(b._ambigTime=!1),a._ambigZone?b._ambigZone=!0:b._ambigZone&&(b._ambigZone=!1)}function na(a,b){a.year(b[0]||0).month(b[1]||0).date(b[2]||0).hours(b[3]||0).minutes(b[4]||0).seconds(b[5]||0).milliseconds(b[6]||0)}function oa(a,b){return gb.format.call(a,b)}function pa(a,b){return qa(a,va(b))}function qa(a,b){var c,d="";for(c=0;c<b.length;c++)d+=ra(a,b[c]);return d}function ra(a,b){var c,d;return"string"==typeof b?b:(c=b.token)?hb[c]?hb[c](a):oa(a,c):b.maybe&&(d=qa(a,b.maybe),d.match(/[1-9]/))?d:""}function sa(a,b,c,d,e){var f;return a=Va.moment.parseZone(a),b=Va.moment.parseZone(b),f=(a.localeData||a.lang).call(a),c=f.longDateFormat(c)||c,d=d||" - ",ta(a,b,va(c),d,e)}function ta(a,b,c,d,e){var f,g,h,i,j=a.clone().stripZone(),k=b.clone().stripZone(),l="",m="",n="",o="",p="";for(g=0;g<c.length&&(f=ua(a,b,j,k,c[g]),f!==!1);g++)l+=f;for(h=c.length-1;h>g&&(f=ua(a,b,j,k,c[h]),f!==!1);h--)m=f+m;for(i=g;h>=i;i++)n+=ra(a,c[i]),o+=ra(b,c[i]);return(n||o)&&(p=e?o+d+n:n+d+o),l+p+m}function ua(a,b,c,d,e){var f,g;return"string"==typeof e?e:(f=e.token)&&(g=ib[f.charAt(0)],g&&c.isSame(d,g))?oa(a,f):!1}function va(a){return a in jb?jb[a]:jb[a]=wa(a)}function wa(a){for(var b,c=[],d=/\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g;b=d.exec(a);)b[1]?c.push(b[1]):b[2]?c.push({maybe:wa(b[2])}):b[3]?c.push({token:b[3]}):b[5]&&c.push(b[5]);return c}function xa(){}function ya(a,b){var c;return $(b,"constructor")&&(c=b.constructor),"function"!=typeof c&&(c=b.constructor=function(){a.apply(this,arguments)}),c.prototype=X(a.prototype),Y(b,c.prototype),Z(b,c.prototype),Y(a,c),c}function za(a,b){Y(b,a.prototype)}function Aa(a,b){return a||b?a&&b?a.component===b.component&&Ba(a,b)&&Ba(b,a):!1:!0}function Ba(a,b){for(var c in a)if(!/^(component|left|right|top|bottom)$/.test(c)&&a[c]!==b[c])return!1;return!0}function Ca(a){var b=Ea(a);return"background"===b||"inverse-background"===b}function Da(a){return"inverse-background"===Ea(a)}function Ea(a){return ba((a.source||{}).rendering,a.rendering)}function Fa(a){var b,c,d={};for(b=0;b<a.length;b++)c=a[b],(d[c._id]||(d[c._id]=[])).push(c);return d}function Ga(a,b){return a.start-b.start}function Ha(c){var d,e,f,g,h=Va.dataAttrPrefix;return h&&(h+="-"),d=c.data(h+"event")||null,d&&(d="object"==typeof d?a.extend({},d):{},e=d.start,null==e&&(e=d.time),f=d.duration,g=d.stick,delete d.start,delete d.time,delete d.duration,delete d.stick),null==e&&(e=c.data(h+"start")),null==e&&(e=c.data(h+"time")),null==f&&(f=c.data(h+"duration")),null==g&&(g=c.data(h+"stick")),e=null!=e?b.duration(e):null,f=null!=f?b.duration(f):null,g=Boolean(g),{eventProps:d,startTime:e,duration:f,stick:g}}function Ia(a,b){var c,d;for(c=0;c<b.length;c++)if(d=b[c],d.leftCol<=a.rightCol&&d.rightCol>=a.leftCol)return!0;return!1}function Ja(a,b){return a.leftCol-b.leftCol}function Ka(a){var b,c,d,e=[];for(b=0;b<a.length;b++){for(c=a[b],d=0;d<e.length&&Na(c,e[d]).length;d++);c.level=d,(e[d]||(e[d]=[])).push(c)}return e}function La(a){var b,c,d,e,f;for(b=0;b<a.length;b++)for(c=a[b],d=0;d<c.length;d++)for(e=c[d],e.forwardSegs=[],f=b+1;f<a.length;f++)Na(e,a[f],e.forwardSegs)}function Ma(a){var b,c,d=a.forwardSegs,e=0;if(void 0===a.forwardPressure){for(b=0;b<d.length;b++)c=d[b],Ma(c),e=Math.max(e,1+c.forwardPressure);a.forwardPressure=e}}function Na(a,b,c){c=c||[];for(var d=0;d<b.length;d++)Oa(a,b[d])&&c.push(b[d]);return c}function Oa(a,b){return a.bottom>b.top&&a.top<b.bottom}function Pa(c,d){function e(){T?h()&&(k(),i()):f()}function f(){U=O.theme?"ui":"fc",c.addClass("fc"),O.isRTL?c.addClass("fc-rtl"):c.addClass("fc-ltr"),O.theme?c.addClass("ui-widget"):c.addClass("fc-unthemed"),T=a("<div class='fc-view-container'/>").prependTo(c),R=N.header=new Sa(N,O),S=R.render(),S&&c.prepend(S),i(O.defaultView),O.handleWindowResize&&(Y=ja(m,O.windowResizeDelay),a(window).resize(Y))}function g(){V&&V.removeElement(),R.removeElement(),T.remove(),c.removeClass("fc fc-ltr fc-rtl fc-unthemed ui-widget"),Y&&a(window).unbind("resize",Y)}function h(){return c.is(":visible")}function i(b){ca++,V&&b&&V.type!==b&&(R.deactivateButton(V.type),H(),V.removeElement(),V=N.view=null),!V&&b&&(V=N.view=ba[b]||(ba[b]=N.instantiateView(b)),V.setElement(a("<div class='fc-view fc-"+b+"-view' />").appendTo(T)),R.activateButton(b)),V&&(Z=V.massageCurrentDate(Z),V.displaying&&Z.isWithin(V.intervalStart,V.intervalEnd)||h()&&(V.display(Z),I(),u(),v(),q())),I(),ca--}function j(a){return h()?(a&&l(),ca++,V.updateSize(!0),ca--,!0):void 0}function k(){h()&&l()}function l(){W="number"==typeof O.contentHeight?O.contentHeight:"number"==typeof O.height?O.height-(S?S.outerHeight(!0):0):Math.round(T.width()/Math.max(O.aspectRatio,.5))}function m(a){!ca&&a.target===window&&V.start&&j(!0)&&V.trigger("windowResize",aa)}function n(){p(),r()}function o(){h()&&(H(),V.displayEvents(da),I())}function p(){H(),V.clearEvents(),I()}function q(){!O.lazyFetching||$(V.start,V.end)?r():o()}function r(){_(V.start,V.end)}function s(a){da=a,o()}function t(){o()}function u(){R.updateTitle(V.title)}function v(){var a=N.getNow();a.isWithin(V.intervalStart,V.intervalEnd)?R.disableButton("today"):R.enableButton("today")}function w(a,b){V.select(N.buildSelectSpan.apply(N,arguments))}function x(){V&&V.unselect()}function y(){Z=V.computePrevDate(Z),i()}function z(){Z=V.computeNextDate(Z),i()}function A(){Z.add(-1,"years"),i()}function B(){Z.add(1,"years"),i()}function C(){Z=N.getNow(),i()}function D(a){Z=N.moment(a).stripZone(),i()}function E(a){Z.add(b.duration(a)),i()}function F(a,b){var c;b=b||"day",c=N.getViewSpec(b)||N.getUnitViewSpec(b),Z=a.clone(),i(c?c.type:null)}function G(){return N.applyTimezone(Z)}function H(){T.css({width:"100%",height:T.height(),overflow:"hidden"})}function I(){T.css({width:"",height:"",overflow:""})}function J(){return N}function K(){return V}function L(a,b){return void 0===b?O[a]:void("height"!=a&&"contentHeight"!=a&&"aspectRatio"!=a||(O[a]=b,j(!0)))}function M(a,b){var c=Array.prototype.slice.call(arguments,2);return b=b||aa,this.triggerWith(a,b,c),O[a]?O[a].apply(b,c):void 0}var N=this;N.initOptions(d||{});var O=this.options;N.render=e,N.destroy=g,N.refetchEvents=n,N.reportEvents=s,N.reportEventChange=t,N.rerenderEvents=o,N.changeView=i,N.select=w,N.unselect=x,N.prev=y,N.next=z,N.prevYear=A,N.nextYear=B,N.today=C,N.gotoDate=D,N.incrementDate=E,N.zoomTo=F,N.getDate=G,N.getCalendar=J,N.getView=K,N.option=L,N.trigger=M;var P=X(Ra(O.lang));if(O.monthNames&&(P._months=O.monthNames),O.monthNamesShort&&(P._monthsShort=O.monthNamesShort),O.dayNames&&(P._weekdays=O.dayNames),O.dayNamesShort&&(P._weekdaysShort=O.dayNamesShort),null!=O.firstDay){var Q=X(P._week);Q.dow=O.firstDay,P._week=Q}P._fullCalendar_weekCalc=function(a){return"function"==typeof a?a:"local"===a?a:"iso"===a||"ISO"===a?"ISO":void 0}(O.weekNumberCalculation),N.defaultAllDayEventDuration=b.duration(O.defaultAllDayEventDuration),N.defaultTimedEventDuration=b.duration(O.defaultTimedEventDuration),N.moment=function(){var a;return"local"===O.timezone?(a=Va.moment.apply(null,arguments),a.hasTime()&&a.local()):a="UTC"===O.timezone?Va.moment.utc.apply(null,arguments):Va.moment.parseZone.apply(null,arguments),"_locale"in a?a._locale=P:a._lang=P,a},N.getIsAmbigTimezone=function(){return"local"!==O.timezone&&"UTC"!==O.timezone},N.applyTimezone=function(a){if(!a.hasTime())return a.clone();var b,c=N.moment(a.toArray()),d=a.time()-c.time();return d&&(b=c.clone().add(d),a.time()-b.time()===0&&(c=b)),c},N.getNow=function(){var a=O.now;return"function"==typeof a&&(a=a()),N.moment(a).stripZone()},N.getEventEnd=function(a){return a.end?a.end.clone():N.getDefaultEventEnd(a.allDay,a.start)},N.getDefaultEventEnd=function(a,b){var c=b.clone();return a?c.stripTime().add(N.defaultAllDayEventDuration):c.add(N.defaultTimedEventDuration),N.getIsAmbigTimezone()&&c.stripZone(),c},N.humanizeDuration=function(a){return(a.locale||a.lang).call(a,O.lang).humanize()},Ta.call(N,O);var R,S,T,U,V,W,Y,Z,$=N.isFetchNeeded,_=N.fetchEvents,aa=c[0],ba={},ca=0,da=[];Z=null!=O.defaultDate?N.moment(O.defaultDate).stripZone():N.getNow(),N.getSuggestedViewHeight=function(){return void 0===W&&k(),W},N.isHeightAuto=function(){return"auto"===O.contentHeight||"auto"===O.height},N.freezeContentHeight=H,N.unfreezeContentHeight=I,N.initialize()}function Qa(b){a.each(Cb,function(a,c){null==b[a]&&(b[a]=c(b))})}function Ra(a){var c=b.localeData||b.langData;return c.call(b,a)||c.call(b,"en")}function Sa(b,c){function d(){var b=c.header;return n=c.theme?"ui":"fc",b?o=a("<div class='fc-toolbar'/>").append(f("left")).append(f("right")).append(f("center")).append('<div class="fc-clear"/>'):void 0}function e(){o.remove(),o=a()}function f(d){var e=a('<div class="fc-'+d+'"/>'),f=c.header[d];return f&&a.each(f.split(" "),function(d){var f,g=a(),h=!0;a.each(this.split(","),function(d,e){var f,i,j,k,l,m,o,q,r,s;"title"==e?(g=g.add(a("<h2>&nbsp;</h2>")),h=!1):((f=(b.options.customButtons||{})[e])?(j=function(a){f.click&&f.click.call(s[0],a)},k="",l=f.text):(i=b.getViewSpec(e))?(j=function(){b.changeView(e)},p.push(e),k=i.buttonTextOverride,l=i.buttonTextDefault):b[e]&&(j=function(){b[e]()},k=(b.overrides.buttonText||{})[e],l=c.buttonText[e]),j&&(m=f?f.themeIcon:c.themeButtonIcons[e],o=f?f.icon:c.buttonIcons[e],q=k?ca(k):m&&c.theme?"<span class='ui-icon ui-icon-"+m+"'></span>":o&&!c.theme?"<span class='fc-icon fc-icon-"+o+"'></span>":ca(l),r=["fc-"+e+"-button",n+"-button",n+"-state-default"],s=a('<button type="button" class="'+r.join(" ")+'">'+q+"</button>").click(function(a){s.hasClass(n+"-state-disabled")||(j(a),(s.hasClass(n+"-state-active")||s.hasClass(n+"-state-disabled"))&&s.removeClass(n+"-state-hover"))}).mousedown(function(){s.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-down")}).mouseup(function(){s.removeClass(n+"-state-down")}).hover(function(){s.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-hover")},function(){s.removeClass(n+"-state-hover").removeClass(n+"-state-down")}),g=g.add(s)))}),h&&g.first().addClass(n+"-corner-left").end().last().addClass(n+"-corner-right").end(),g.length>1?(f=a("<div/>"),h&&f.addClass("fc-button-group"),f.append(g),e.append(f)):e.append(g)}),e}function g(a){o.find("h2").text(a)}function h(a){o.find(".fc-"+a+"-button").addClass(n+"-state-active")}function i(a){o.find(".fc-"+a+"-button").removeClass(n+"-state-active")}function j(a){o.find(".fc-"+a+"-button").attr("disabled","disabled").addClass(n+"-state-disabled")}function k(a){o.find(".fc-"+a+"-button").removeAttr("disabled").removeClass(n+"-state-disabled")}function l(){return p}var m=this;m.render=d,m.removeElement=e,m.updateTitle=g,m.activateButton=h,m.deactivateButton=i,m.disableButton=j,m.enableButton=k,m.getViewsWithButtons=l;var n,o=a(),p=[]}function Ta(c){function d(a,b){return!I||I>a||b>J}function e(a,b){I=a,J=b,S=[];var c=++Q,d=P.length;R=d;for(var e=0;d>e;e++)f(P[e],c)}function f(b,c){g(b,function(d){var e,f,g,h=a.isArray(b.events);if(c==Q){if(d)for(e=0;e<d.length;e++)f=d[e],g=h?f:s(f,b),g&&S.push.apply(S,w(g));R--,R||K(S)}})}function g(b,d){var e,f,h=Va.sourceFetchers;for(e=0;e<h.length;e++){if(f=h[e].call(H,b,I.clone(),J.clone(),c.timezone,d),f===!0)return;if("object"==typeof f)return void g(f,d)}var i=b.events;if(i)a.isFunction(i)?(H.pushLoading(),i.call(H,I.clone(),J.clone(),c.timezone,function(a){d(a),H.popLoading()})):a.isArray(i)?d(i):d();else{var j=b.url;if(j){var k,l=b.success,m=b.error,n=b.complete;k=a.isFunction(b.data)?b.data():b.data;var o=a.extend({},k||{}),p=ba(b.startParam,c.startParam),q=ba(b.endParam,c.endParam),r=ba(b.timezoneParam,c.timezoneParam);p&&(o[p]=I.format()),q&&(o[q]=J.format()),c.timezone&&"local"!=c.timezone&&(o[r]=c.timezone),H.pushLoading(),a.ajax(a.extend({},Db,b,{data:o,success:function(b){b=b||[];var c=aa(l,this,arguments);a.isArray(c)&&(b=c),d(b)},error:function(){aa(m,this,arguments),d()},complete:function(){aa(n,this,arguments),H.popLoading()}}))}else d()}}function h(a){var b=i(a);b&&(P.push(b),R++,f(b,Q))}function i(b){var c,d,e=Va.sourceNormalizers;if(a.isFunction(b)||a.isArray(b)?c={events:b}:"string"==typeof b?c={url:b}:"object"==typeof b&&(c=a.extend({},b)),c){for(c.className?"string"==typeof c.className&&(c.className=c.className.split(/\s+/)):c.className=[],a.isArray(c.events)&&(c.origArray=c.events,c.events=a.map(c.events,function(a){return s(a,c)})),d=0;d<e.length;d++)e[d].call(H,c);return c}}function j(b){P=a.grep(P,function(a){return!k(a,b)}),S=a.grep(S,function(a){return!k(a.source,b)}),K(S)}function k(a,b){return a&&b&&l(a)==l(b)}function l(a){return("object"==typeof a?a.origArray||a.googleCalendarId||a.url||a.events:null)||a}function m(a){a.start=H.moment(a.start),a.end?a.end=H.moment(a.end):a.end=null,x(a,n(a)),K(S)}function n(b){var c={};return a.each(b,function(a,b){o(a)&&void 0!==b&&_(b)&&(c[a]=b)}),c}function o(a){return!/^_|^(id|allDay|start|end)$/.test(a)}function p(a,b){var c,d,e,f=s(a);if(f){for(c=w(f),d=0;d<c.length;d++)e=c[d],e.source||(b&&(O.events.push(e),e.source=O),S.push(e));return K(S),c}return[]}function q(b){var c,d;for(null==b?b=function(){return!0}:a.isFunction(b)||(c=b+"",b=function(a){return a._id==c}),S=a.grep(S,b,!0),d=0;d<P.length;d++)a.isArray(P[d].events)&&(P[d].events=a.grep(P[d].events,b,!0));K(S)}function r(b){return a.isFunction(b)?a.grep(S,b):null!=b?(b+="",a.grep(S,function(a){return a._id==b})):S}function s(d,e){var f,g,h,i={};if(c.eventDataTransform&&(d=c.eventDataTransform(d)),e&&e.eventDataTransform&&(d=e.eventDataTransform(d)),a.extend(i,d),e&&(i.source=e),i._id=d._id||(void 0===d.id?"_fc"+Eb++:d.id+""),d.className?"string"==typeof d.className?i.className=d.className.split(/\s+/):i.className=d.className:i.className=[],f=d.start||d.date,g=d.end,V(f)&&(f=b.duration(f)),V(g)&&(g=b.duration(g)),d.dow||b.isDuration(f)||b.isDuration(g))i.start=f?b.duration(f):null,i.end=g?b.duration(g):null,i._recurring=!0;else{if(f&&(f=H.moment(f),!f.isValid()))return!1;g&&(g=H.moment(g),g.isValid()||(g=null)),h=d.allDay,void 0===h&&(h=ba(e?e.allDayDefault:void 0,c.allDayDefault)),t(f,g,h,i)}return i}function t(a,b,c,d){d.start=a,d.end=b,d.allDay=c,u(d),Ua(d)}function u(a){v(a),a.end&&!a.end.isAfter(a.start)&&(a.end=null),a.end||(c.forceEventDuration?a.end=H.getDefaultEventEnd(a.allDay,a.start):a.end=null)}function v(a){null==a.allDay&&(a.allDay=!(a.start.hasTime()||a.end&&a.end.hasTime())),a.allDay?(a.start.stripTime(),a.end&&a.end.stripTime()):(a.start.hasTime()||(a.start=H.applyTimezone(a.start.time(0))),a.end&&!a.end.hasTime()&&(a.end=H.applyTimezone(a.end.time(0))))}function w(b,c,d){var e,f,g,h,i,j,k,l,m,n=[];if(c=c||I,d=d||J,b)if(b._recurring){if(f=b.dow)for(e={},g=0;g<f.length;g++)e[f[g]]=!0;for(h=c.clone().stripTime();h.isBefore(d);)e&&!e[h.day()]||(i=b.start,j=b.end,k=h.clone(),l=null,i&&(k=k.time(i)),j&&(l=h.clone().time(j)),m=a.extend({},b),t(k,l,!i&&!j,m),n.push(m)),h.add(1,"days")}else n.push(b);return n}function x(b,c,d){function e(a,b){return d?N(a,b,d):c.allDay?M(a,b):L(a,b)}var f,g,h,i,j,k,l={};return c=c||{},c.start||(c.start=b.start.clone()),void 0===c.end&&(c.end=b.end?b.end.clone():null),null==c.allDay&&(c.allDay=b.allDay),u(c),f={start:b._start.clone(),end:b._end?b._end.clone():H.getDefaultEventEnd(b._allDay,b._start),allDay:c.allDay},u(f),g=null!==b._end&&null===c.end,h=e(c.start,f.start),c.end?(i=e(c.end,f.end),j=i.subtract(h)):j=null,a.each(c,function(a,b){o(a)&&void 0!==b&&(l[a]=b)}),k=y(r(b._id),g,c.allDay,h,j,l),{dateDelta:h,durationDelta:j,undo:k}}function y(b,c,d,e,f,g){var h=H.getIsAmbigTimezone(),i=[];return e&&!e.valueOf()&&(e=null),f&&!f.valueOf()&&(f=null),a.each(b,function(b,j){var k,l;k={start:j.start.clone(),end:j.end?j.end.clone():null,allDay:j.allDay},a.each(g,function(a){k[a]=j[a]}),l={start:j._start,end:j._end,allDay:d},u(l),c?l.end=null:f&&!l.end&&(l.end=H.getDefaultEventEnd(l.allDay,l.start)),e&&(l.start.add(e),l.end&&l.end.add(e)),f&&l.end.add(f),h&&!l.allDay&&(e||f)&&(l.start.stripZone(),l.end&&l.end.stripZone()),a.extend(j,g,l),Ua(j),i.push(function(){a.extend(j,k),Ua(j)})}),function(){for(var a=0;a<i.length;a++)i[a]()}}function z(b){var d,e=c.businessHours,f={className:"fc-nonbusiness",start:"09:00",end:"17:00",dow:[1,2,3,4,5],rendering:"inverse-background"},g=H.getView();return e&&(d=a.extend({},f,"object"==typeof e?e:{})),d?(b&&(d.start=null,d.end=null),w(s(d),g.start,g.end)):[]}function A(a,b){var d=b.source||{},e=ba(b.constraint,d.constraint,c.eventConstraint),f=ba(b.overlap,d.overlap,c.eventOverlap);return D(a,e,f,b)}function B(b,c,d){var e,f;return d&&(e=a.extend({},d,c),f=w(s(e))[0]),f?A(b,f):C(b)}function C(a){return D(a,c.selectConstraint,c.selectOverlap)}function D(a,b,c,d){var e,f,g,h,i,j;if(null!=b){for(e=E(b),f=!1,h=0;h<e.length;h++)if(F(e[h],a)){f=!0;break}if(!f)return!1}for(g=H.getPeerEvents(a,d),h=0;h<g.length;h++)if(i=g[h],G(i,a)){if(c===!1)return!1;if("function"==typeof c&&!c(i,d))return!1;if(d){if(j=ba(i.overlap,(i.source||{}).overlap),j===!1)return!1;if("function"==typeof j&&!j(d,i))return!1}}return!0}function E(a){return"businessHours"===a?z():"object"==typeof a?w(s(a)):r(a)}function F(a,b){var c=a.start.clone().stripZone(),d=H.getEventEnd(a).stripZone();return b.start>=c&&b.end<=d}function G(a,b){var c=a.start.clone().stripZone(),d=H.getEventEnd(a).stripZone();return b.start<d&&b.end>c}var H=this;H.isFetchNeeded=d,H.fetchEvents=e,H.addEventSource=h,H.removeEventSource=j,H.updateEvent=m,H.renderEvent=p,H.removeEvents=q,H.clientEvents=r,H.mutateEvent=x,H.normalizeEventDates=u,H.normalizeEventTimes=v;var I,J,K=H.reportEvents,O={events:[]},P=[O],Q=0,R=0,S=[];a.each((c.events?[c.events]:[]).concat(c.eventSources||[]),function(a,b){var c=i(b);c&&P.push(c)}),H.getBusinessHoursEvents=z,H.isEventSpanAllowed=A,H.isExternalSpanAllowed=B,H.isSelectionSpanAllowed=C,H.getEventCache=function(){return S}}function Ua(a){a._allDay=a.allDay,a._start=a.start.clone(),a._end=a.end?a.end.clone():null}var Va=a.fullCalendar={version:"2.7.2",internalApiVersion:3},Wa=Va.views={};a.fn.fullCalendar=function(b){var c=Array.prototype.slice.call(arguments,1),d=this;return this.each(function(e,f){var g,h=a(f),i=h.data("fullCalendar");"string"==typeof b?i&&a.isFunction(i[b])&&(g=i[b].apply(i,c),e||(d=g),"destroy"===b&&h.removeData("fullCalendar")):i||(i=new yb(h,b),h.data("fullCalendar",i),i.render())}),d};var Xa=["header","buttonText","buttonIcons","themeButtonIcons"];Va.intersectRanges=K,Va.applyAll=aa,Va.debounce=ja,Va.isInt=ha,Va.htmlEscape=ca,Va.cssToStr=ea,Va.proxy=ia,Va.capitaliseFirstLetter=fa,Va.getOuterRect=n,Va.getClientRect=o,Va.getContentRect=p,Va.getScrollbarWidths=q;var Ya=null;Va.preventDefault=z,Va.intersectRects=C,Va.parseFieldSpecs=G,Va.compareByFieldSpecs=H,Va.compareByFieldSpec=I,Va.flexibleCompare=J,Va.computeIntervalUnit=O,Va.divideRangeByDuration=Q,Va.divideDurationByDuration=R,Va.multiplyDuration=S,Va.durationHasTime=T;var Za=["sun","mon","tue","wed","thu","fri","sat"],$a=["year","month","week","day","hour","minute","second","millisecond"];Va.log=function(){var a=window.console;return a&&a.log?a.log.apply(a,arguments):void 0},Va.warn=function(){var a=window.console;return a&&a.warn?a.warn.apply(a,arguments):Va.log.apply(Va,arguments)};var _a,ab,bb,cb={}.hasOwnProperty,db=/^\s*\d{4}-\d\d$/,eb=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/,fb=b.fn,gb=a.extend({},fb);Va.moment=function(){return ka(arguments)},Va.moment.utc=function(){var a=ka(arguments,!0);return a.hasTime()&&a.utc(),a},Va.moment.parseZone=function(){return ka(arguments,!0,!0)},fb.clone=function(){var a=gb.clone.apply(this,arguments);return ma(this,a),this._fullCalendar&&(a._fullCalendar=!0),a},fb.week=fb.weeks=function(a){var b=(this._locale||this._lang)._fullCalendar_weekCalc;return null==a&&"function"==typeof b?b(this):"ISO"===b?gb.isoWeek.apply(this,arguments):gb.week.apply(this,arguments)},fb.time=function(a){if(!this._fullCalendar)return gb.time.apply(this,arguments);if(null==a)return b.duration({hours:this.hours(),minutes:this.minutes(),seconds:this.seconds(),milliseconds:this.milliseconds()});this._ambigTime=!1,b.isDuration(a)||b.isMoment(a)||(a=b.duration(a));var c=0;return b.isDuration(a)&&(c=24*Math.floor(a.asDays())),this.hours(c+a.hours()).minutes(a.minutes()).seconds(a.seconds()).milliseconds(a.milliseconds())},fb.stripTime=function(){var a;return this._ambigTime||(a=this.toArray(),this.utc(),ab(this,a.slice(0,3)),this._ambigTime=!0,this._ambigZone=!0),this},fb.hasTime=function(){return!this._ambigTime},fb.stripZone=function(){var a,b;return this._ambigZone||(a=this.toArray(),b=this._ambigTime,this.utc(),ab(this,a),this._ambigTime=b||!1,this._ambigZone=!0),this},fb.hasZone=function(){return!this._ambigZone},fb.local=function(){var a=this.toArray(),b=this._ambigZone;return gb.local.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,b&&bb(this,a),this},fb.utc=function(){return gb.utc.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,this},a.each(["zone","utcOffset"],function(a,b){gb[b]&&(fb[b]=function(a){return null!=a&&(this._ambigTime=!1,this._ambigZone=!1),gb[b].apply(this,arguments)})}),fb.format=function(){return this._fullCalendar&&arguments[0]?pa(this,arguments[0]):this._ambigTime?oa(this,"YYYY-MM-DD"):this._ambigZone?oa(this,"YYYY-MM-DD[T]HH:mm:ss"):gb.format.apply(this,arguments)},fb.toISOString=function(){return this._ambigTime?oa(this,"YYYY-MM-DD"):this._ambigZone?oa(this,"YYYY-MM-DD[T]HH:mm:ss"):gb.toISOString.apply(this,arguments)},fb.isWithin=function(a,b){var c=la([this,a,b]);return c[0]>=c[1]&&c[0]<c[2]},fb.isSame=function(a,b){var c;return this._fullCalendar?b?(c=la([this,a],!0),gb.isSame.call(c[0],c[1],b)):(a=Va.moment.parseZone(a),gb.isSame.call(this,a)&&Boolean(this._ambigTime)===Boolean(a._ambigTime)&&Boolean(this._ambigZone)===Boolean(a._ambigZone)):gb.isSame.apply(this,arguments)},a.each(["isBefore","isAfter"],function(a,b){fb[b]=function(a,c){var d;return this._fullCalendar?(d=la([this,a]),gb[b].call(d[0],d[1],c)):gb[b].apply(this,arguments)}}),_a="_d"in b()&&"updateOffset"in b,ab=_a?function(a,c){a._d.setTime(Date.UTC.apply(Date,c)),b.updateOffset(a,!1)}:na,bb=_a?function(a,c){a._d.setTime(+new Date(c[0]||0,c[1]||0,c[2]||0,c[3]||0,c[4]||0,c[5]||0,c[6]||0)),b.updateOffset(a,!1)}:na;var hb={t:function(a){return oa(a,"a").charAt(0)},T:function(a){return oa(a,"A").charAt(0)}};Va.formatRange=sa;var ib={Y:"year",M:"month",D:"day",d:"day",A:"second",a:"second",T:"second",t:"second",H:"second",h:"second",m:"second",s:"second"},jb={};Va.Class=xa,xa.extend=function(){var a,b,c=arguments.length;for(a=0;c>a;a++)b=arguments[a],c-1>a&&za(this,b);return ya(this,b||{})},xa.mixin=function(a){za(this,a)};var kb=Va.EmitterMixin={callbackHash:null,on:function(a,b){return this.loopCallbacks(a,"add",[b]),this},off:function(a,b){return this.loopCallbacks(a,"remove",[b]),this},trigger:function(a){var b=Array.prototype.slice.call(arguments,1);return this.triggerWith(a,this,b),this},triggerWith:function(a,b,c){return this.loopCallbacks(a,"fireWith",[b,c]),this},loopCallbacks:function(a,b,c){var d,e,f,g=a.split(".");for(d=0;d<g.length;d++)e=g[d],e&&(f=this.ensureCallbackObj((d?".":"")+e),f[b].apply(f,c))},ensureCallbackObj:function(b){return this.callbackHash||(this.callbackHash={}),this.callbackHash[b]||(this.callbackHash[b]=a.Callbacks()),this.callbackHash[b]}},lb=Va.ListenerMixin=function(){var b=0,c={listenerId:null,listenTo:function(b,c,d){if("object"==typeof c)for(var e in c)c.hasOwnProperty(e)&&this.listenTo(b,e,c[e]);else"string"==typeof c&&b.on(c+"."+this.getListenerNamespace(),a.proxy(d,this))},stopListeningTo:function(a,b){a.off((b||"")+"."+this.getListenerNamespace())},getListenerNamespace:function(){return null==this.listenerId&&(this.listenerId=b++),"_listener"+this.listenerId}};return c}(),mb={isIgnoringMouse:!1,delayUnignoreMouse:null,
+initMouseIgnoring:function(a){this.delayUnignoreMouse=ja(ia(this,"unignoreMouse"),a||1e3)},tempIgnoreMouse:function(){this.isIgnoringMouse=!0,this.delayUnignoreMouse()},unignoreMouse:function(){this.isIgnoringMouse=!1}},nb=xa.extend(lb,{isHidden:!0,options:null,el:null,margin:10,constructor:function(a){this.options=a||{}},show:function(){this.isHidden&&(this.el||this.render(),this.el.show(),this.position(),this.isHidden=!1,this.trigger("show"))},hide:function(){this.isHidden||(this.el.hide(),this.isHidden=!0,this.trigger("hide"))},render:function(){var b=this,c=this.options;this.el=a('<div class="fc-popover"/>').addClass(c.className||"").css({top:0,left:0}).append(c.content).appendTo(c.parentEl),this.el.on("click",".fc-close",function(){b.hide()}),c.autoHide&&this.listenTo(a(document),"mousedown",this.documentMousedown)},documentMousedown:function(b){this.el&&!a(b.target).closest(this.el).length&&this.hide()},removeElement:function(){this.hide(),this.el&&(this.el.remove(),this.el=null),this.stopListeningTo(a(document),"mousedown")},position:function(){var b,c,d,e,f,g=this.options,h=this.el.offsetParent().offset(),i=this.el.outerWidth(),j=this.el.outerHeight(),k=a(window),l=m(this.el);e=g.top||0,f=void 0!==g.left?g.left:void 0!==g.right?g.right-i:0,l.is(window)||l.is(document)?(l=k,b=0,c=0):(d=l.offset(),b=d.top,c=d.left),b+=k.scrollTop(),c+=k.scrollLeft(),g.viewportConstrain!==!1&&(e=Math.min(e,b+l.outerHeight()-j-this.margin),e=Math.max(e,b+this.margin),f=Math.min(f,c+l.outerWidth()-i-this.margin),f=Math.max(f,c+this.margin)),this.el.css({top:e-h.top,left:f-h.left})},trigger:function(a){this.options[a]&&this.options[a].apply(this,Array.prototype.slice.call(arguments,1))}}),ob=Va.CoordCache=xa.extend({els:null,forcedOffsetParentEl:null,origin:null,boundingRect:null,isHorizontal:!1,isVertical:!1,lefts:null,rights:null,tops:null,bottoms:null,constructor:function(b){this.els=a(b.els),this.isHorizontal=b.isHorizontal,this.isVertical=b.isVertical,this.forcedOffsetParentEl=b.offsetParent?a(b.offsetParent):null},build:function(){var a=this.forcedOffsetParentEl||this.els.eq(0).offsetParent();this.origin=a.offset(),this.boundingRect=this.queryBoundingRect(),this.isHorizontal&&this.buildElHorizontals(),this.isVertical&&this.buildElVerticals()},clear:function(){this.origin=null,this.boundingRect=null,this.lefts=null,this.rights=null,this.tops=null,this.bottoms=null},ensureBuilt:function(){this.origin||this.build()},queryBoundingRect:function(){var a=m(this.els.eq(0));return a.is(document)?void 0:o(a)},buildElHorizontals:function(){var b=[],c=[];this.els.each(function(d,e){var f=a(e),g=f.offset().left,h=f.outerWidth();b.push(g),c.push(g+h)}),this.lefts=b,this.rights=c},buildElVerticals:function(){var b=[],c=[];this.els.each(function(d,e){var f=a(e),g=f.offset().top,h=f.outerHeight();b.push(g),c.push(g+h)}),this.tops=b,this.bottoms=c},getHorizontalIndex:function(a){this.ensureBuilt();var b,c=this.boundingRect,d=this.lefts,e=this.rights,f=d.length;if(!c||a>=c.left&&a<c.right)for(b=0;f>b;b++)if(a>=d[b]&&a<e[b])return b},getVerticalIndex:function(a){this.ensureBuilt();var b,c=this.boundingRect,d=this.tops,e=this.bottoms,f=d.length;if(!c||a>=c.top&&a<c.bottom)for(b=0;f>b;b++)if(a>=d[b]&&a<e[b])return b},getLeftOffset:function(a){return this.ensureBuilt(),this.lefts[a]},getLeftPosition:function(a){return this.ensureBuilt(),this.lefts[a]-this.origin.left},getRightOffset:function(a){return this.ensureBuilt(),this.rights[a]},getRightPosition:function(a){return this.ensureBuilt(),this.rights[a]-this.origin.left},getWidth:function(a){return this.ensureBuilt(),this.rights[a]-this.lefts[a]},getTopOffset:function(a){return this.ensureBuilt(),this.tops[a]},getTopPosition:function(a){return this.ensureBuilt(),this.tops[a]-this.origin.top},getBottomOffset:function(a){return this.ensureBuilt(),this.bottoms[a]},getBottomPosition:function(a){return this.ensureBuilt(),this.bottoms[a]-this.origin.top},getHeight:function(a){return this.ensureBuilt(),this.bottoms[a]-this.tops[a]}}),pb=Va.DragListener=xa.extend(lb,mb,{options:null,subjectEl:null,subjectHref:null,originX:null,originY:null,scrollEl:null,isInteracting:!1,isDistanceSurpassed:!1,isDelayEnded:!1,isDragging:!1,isTouch:!1,delay:null,delayTimeoutId:null,minDistance:null,handleTouchScrollProxy:null,constructor:function(a){this.options=a||{},this.handleTouchScrollProxy=ia(this,"handleTouchScroll"),this.initMouseIgnoring(500)},startInteraction:function(b,c){var d=x(b);if("mousedown"===b.type){if(this.isIgnoringMouse)return;if(!u(b))return;b.preventDefault()}this.isInteracting||(c=c||{},this.delay=ba(c.delay,this.options.delay,0),this.minDistance=ba(c.distance,this.options.distance,0),this.subjectEl=this.options.subjectEl,this.isInteracting=!0,this.isTouch=d,this.isDelayEnded=!1,this.isDistanceSurpassed=!1,this.originX=v(b),this.originY=w(b),this.scrollEl=m(a(b.target)),this.bindHandlers(),this.initAutoScroll(),this.handleInteractionStart(b),this.startDelay(b),this.minDistance||this.handleDistanceSurpassed(b))},handleInteractionStart:function(a){this.trigger("interactionStart",a)},endInteraction:function(a,b){this.isInteracting&&(this.endDrag(a),this.delayTimeoutId&&(clearTimeout(this.delayTimeoutId),this.delayTimeoutId=null),this.destroyAutoScroll(),this.unbindHandlers(),this.isInteracting=!1,this.handleInteractionEnd(a,b),this.isTouch&&this.tempIgnoreMouse())},handleInteractionEnd:function(a,b){this.trigger("interactionEnd",a,b||!1)},bindHandlers:function(){var b=this,c=1;this.isTouch?(this.listenTo(a(document),{touchmove:this.handleTouchMove,touchend:this.endInteraction,touchcancel:this.endInteraction,touchstart:function(a){c?c--:b.endInteraction(a,!0)}}),!A(this.handleTouchScrollProxy)&&this.scrollEl&&this.listenTo(this.scrollEl,"scroll",this.handleTouchScroll)):this.listenTo(a(document),{mousemove:this.handleMouseMove,mouseup:this.endInteraction}),this.listenTo(a(document),{selectstart:z,contextmenu:z})},unbindHandlers:function(){this.stopListeningTo(a(document)),B(this.handleTouchScrollProxy),this.scrollEl&&this.stopListeningTo(this.scrollEl,"scroll")},startDrag:function(a,b){this.startInteraction(a,b),this.isDragging||(this.isDragging=!0,this.handleDragStart(a))},handleDragStart:function(a){this.trigger("dragStart",a),this.initHrefHack()},handleMove:function(a){var b,c=v(a)-this.originX,d=w(a)-this.originY,e=this.minDistance;this.isDistanceSurpassed||(b=c*c+d*d,b>=e*e&&this.handleDistanceSurpassed(a)),this.isDragging&&this.handleDrag(c,d,a)},handleDrag:function(a,b,c){this.trigger("drag",a,b,c),this.updateAutoScroll(c)},endDrag:function(a){this.isDragging&&(this.isDragging=!1,this.handleDragEnd(a))},handleDragEnd:function(a){this.trigger("dragEnd",a),this.destroyHrefHack()},startDelay:function(a){var b=this;this.delay?this.delayTimeoutId=setTimeout(function(){b.handleDelayEnd(a)},this.delay):this.handleDelayEnd(a)},handleDelayEnd:function(a){this.isDelayEnded=!0,this.isDistanceSurpassed&&this.startDrag(a)},handleDistanceSurpassed:function(a){this.isDistanceSurpassed=!0,this.isDelayEnded&&this.startDrag(a)},handleTouchMove:function(a){this.isDragging&&a.preventDefault(),this.handleMove(a)},handleMouseMove:function(a){this.handleMove(a)},handleTouchScroll:function(a){this.isDragging||this.endInteraction(a,!0)},initHrefHack:function(){var a=this.subjectEl;(this.subjectHref=a?a.attr("href"):null)&&a.removeAttr("href")},destroyHrefHack:function(){var a=this.subjectEl,b=this.subjectHref;setTimeout(function(){b&&a.attr("href",b)},0)},trigger:function(a){this.options[a]&&this.options[a].apply(this,Array.prototype.slice.call(arguments,1)),this["_"+a]&&this["_"+a].apply(this,Array.prototype.slice.call(arguments,1))}});pb.mixin({isAutoScroll:!1,scrollBounds:null,scrollTopVel:null,scrollLeftVel:null,scrollIntervalId:null,scrollSensitivity:30,scrollSpeed:200,scrollIntervalMs:50,initAutoScroll:function(){var a=this.scrollEl;this.isAutoScroll=this.options.scroll&&a&&!a.is(window)&&!a.is(document),this.isAutoScroll&&this.listenTo(a,"scroll",ja(this.handleDebouncedScroll,100))},destroyAutoScroll:function(){this.endAutoScroll(),this.isAutoScroll&&this.stopListeningTo(this.scrollEl,"scroll")},computeScrollBounds:function(){this.isAutoScroll&&(this.scrollBounds=n(this.scrollEl))},updateAutoScroll:function(a){var b,c,d,e,f=this.scrollSensitivity,g=this.scrollBounds,h=0,i=0;g&&(b=(f-(w(a)-g.top))/f,c=(f-(g.bottom-w(a)))/f,d=(f-(v(a)-g.left))/f,e=(f-(g.right-v(a)))/f,b>=0&&1>=b?h=b*this.scrollSpeed*-1:c>=0&&1>=c&&(h=c*this.scrollSpeed),d>=0&&1>=d?i=d*this.scrollSpeed*-1:e>=0&&1>=e&&(i=e*this.scrollSpeed)),this.setScrollVel(h,i)},setScrollVel:function(a,b){this.scrollTopVel=a,this.scrollLeftVel=b,this.constrainScrollVel(),!this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(ia(this,"scrollIntervalFunc"),this.scrollIntervalMs))},constrainScrollVel:function(){var a=this.scrollEl;this.scrollTopVel<0?a.scrollTop()<=0&&(this.scrollTopVel=0):this.scrollTopVel>0&&a.scrollTop()+a[0].clientHeight>=a[0].scrollHeight&&(this.scrollTopVel=0),this.scrollLeftVel<0?a.scrollLeft()<=0&&(this.scrollLeftVel=0):this.scrollLeftVel>0&&a.scrollLeft()+a[0].clientWidth>=a[0].scrollWidth&&(this.scrollLeftVel=0)},scrollIntervalFunc:function(){var a=this.scrollEl,b=this.scrollIntervalMs/1e3;this.scrollTopVel&&a.scrollTop(a.scrollTop()+this.scrollTopVel*b),this.scrollLeftVel&&a.scrollLeft(a.scrollLeft()+this.scrollLeftVel*b),this.constrainScrollVel(),this.scrollTopVel||this.scrollLeftVel||this.endAutoScroll()},endAutoScroll:function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.handleScrollEnd())},handleDebouncedScroll:function(){this.scrollIntervalId||this.handleScrollEnd()},handleScrollEnd:function(){}});var qb=pb.extend({component:null,origHit:null,hit:null,coordAdjust:null,constructor:function(a,b){pb.call(this,b),this.component=a},handleInteractionStart:function(a){var b,c,d,e=this.subjectEl;this.computeCoords(),a?(c={left:v(a),top:w(a)},d=c,e&&(b=n(e),d=D(d,b)),this.origHit=this.queryHit(d.left,d.top),e&&this.options.subjectCenter&&(this.origHit&&(b=C(this.origHit,b)||b),d=E(b)),this.coordAdjust=F(d,c)):(this.origHit=null,this.coordAdjust=null),pb.prototype.handleInteractionStart.apply(this,arguments)},computeCoords:function(){this.component.prepareHits(),this.computeScrollBounds()},handleDragStart:function(a){var b;pb.prototype.handleDragStart.apply(this,arguments),b=this.queryHit(v(a),w(a)),b&&this.handleHitOver(b)},handleDrag:function(a,b,c){var d;pb.prototype.handleDrag.apply(this,arguments),d=this.queryHit(v(c),w(c)),Aa(d,this.hit)||(this.hit&&this.handleHitOut(),d&&this.handleHitOver(d))},handleDragEnd:function(){this.handleHitDone(),pb.prototype.handleDragEnd.apply(this,arguments)},handleHitOver:function(a){var b=Aa(a,this.origHit);this.hit=a,this.trigger("hitOver",this.hit,b,this.origHit)},handleHitOut:function(){this.hit&&(this.trigger("hitOut",this.hit),this.handleHitDone(),this.hit=null)},handleHitDone:function(){this.hit&&this.trigger("hitDone",this.hit)},handleInteractionEnd:function(){pb.prototype.handleInteractionEnd.apply(this,arguments),this.origHit=null,this.hit=null,this.component.releaseHits()},handleScrollEnd:function(){pb.prototype.handleScrollEnd.apply(this,arguments),this.computeCoords()},queryHit:function(a,b){return this.coordAdjust&&(a+=this.coordAdjust.left,b+=this.coordAdjust.top),this.component.queryHit(a,b)}}),rb=xa.extend(lb,{options:null,sourceEl:null,el:null,parentEl:null,top0:null,left0:null,y0:null,x0:null,topDelta:null,leftDelta:null,isFollowing:!1,isHidden:!1,isAnimating:!1,constructor:function(b,c){this.options=c=c||{},this.sourceEl=b,this.parentEl=c.parentEl?a(c.parentEl):b.parent()},start:function(b){this.isFollowing||(this.isFollowing=!0,this.y0=w(b),this.x0=v(b),this.topDelta=0,this.leftDelta=0,this.isHidden||this.updatePosition(),x(b)?this.listenTo(a(document),"touchmove",this.handleMove):this.listenTo(a(document),"mousemove",this.handleMove))},stop:function(b,c){function d(){this.isAnimating=!1,e.removeElement(),this.top0=this.left0=null,c&&c()}var e=this,f=this.options.revertDuration;this.isFollowing&&!this.isAnimating&&(this.isFollowing=!1,this.stopListeningTo(a(document)),b&&f&&!this.isHidden?(this.isAnimating=!0,this.el.animate({top:this.top0,left:this.left0},{duration:f,complete:d})):d())},getEl:function(){var a=this.el;return a||(this.sourceEl.width(),a=this.el=this.sourceEl.clone().addClass(this.options.additionalClass||"").css({position:"absolute",visibility:"",display:this.isHidden?"none":"",margin:0,right:"auto",bottom:"auto",width:this.sourceEl.width(),height:this.sourceEl.height(),opacity:this.options.opacity||"",zIndex:this.options.zIndex}),a.addClass("fc-unselectable"),a.appendTo(this.parentEl)),a},removeElement:function(){this.el&&(this.el.remove(),this.el=null)},updatePosition:function(){var a,b;this.getEl(),null===this.top0&&(this.sourceEl.width(),a=this.sourceEl.offset(),b=this.el.offsetParent().offset(),this.top0=a.top-b.top,this.left0=a.left-b.left),this.el.css({top:this.top0+this.topDelta,left:this.left0+this.leftDelta})},handleMove:function(a){this.topDelta=w(a)-this.y0,this.leftDelta=v(a)-this.x0,this.isHidden||this.updatePosition()},hide:function(){this.isHidden||(this.isHidden=!0,this.el&&this.el.hide())},show:function(){this.isHidden&&(this.isHidden=!1,this.updatePosition(),this.getEl().show())}}),sb=Va.Grid=xa.extend(lb,mb,{view:null,isRTL:null,start:null,end:null,el:null,elsByFill:null,eventTimeFormat:null,displayEventTime:null,displayEventEnd:null,minResizeDuration:null,largeUnit:null,dayDragListener:null,segDragListener:null,segResizeListener:null,externalDragListener:null,constructor:function(a){this.view=a,this.isRTL=a.opt("isRTL"),this.elsByFill={},this.dayDragListener=this.buildDayDragListener(),this.initMouseIgnoring()},computeEventTimeFormat:function(){return this.view.opt("smallTimeFormat")},computeDisplayEventTime:function(){return!0},computeDisplayEventEnd:function(){return!0},setRange:function(a){this.start=a.start.clone(),this.end=a.end.clone(),this.rangeUpdated(),this.processRangeOptions()},rangeUpdated:function(){},processRangeOptions:function(){var a,b,c=this.view;this.eventTimeFormat=c.opt("eventTimeFormat")||c.opt("timeFormat")||this.computeEventTimeFormat(),a=c.opt("displayEventTime"),null==a&&(a=this.computeDisplayEventTime()),b=c.opt("displayEventEnd"),null==b&&(b=this.computeDisplayEventEnd()),this.displayEventTime=a,this.displayEventEnd=b},spanToSegs:function(a){},diffDates:function(a,b){return this.largeUnit?N(a,b,this.largeUnit):L(a,b)},prepareHits:function(){},releaseHits:function(){},queryHit:function(a,b){},getHitSpan:function(a){},getHitEl:function(a){},setElement:function(a){this.el=a,y(a),this.bindDayHandler("touchstart",this.dayTouchStart),this.bindDayHandler("mousedown",this.dayMousedown),this.bindSegHandlers(),this.bindGlobalHandlers()},bindDayHandler:function(b,c){var d=this;this.el.on(b,function(b){return a(b.target).is(".fc-event-container *, .fc-more")||a(b.target).closest(".fc-popover").length?void 0:c.call(d,b)})},removeElement:function(){this.unbindGlobalHandlers(),this.clearDragListeners(),this.el.remove()},renderSkeleton:function(){},renderDates:function(){},unrenderDates:function(){},bindGlobalHandlers:function(){this.listenTo(a(document),{dragstart:this.externalDragStart,sortstart:this.externalDragStart})},unbindGlobalHandlers:function(){this.stopListeningTo(a(document))},dayMousedown:function(a){this.isIgnoringMouse||this.dayDragListener.startInteraction(a,{})},dayTouchStart:function(a){var b=this.view;(b.isSelected||b.selectedEvent)&&this.tempIgnoreMouse(),this.dayDragListener.startInteraction(a,{delay:this.view.opt("longPressDelay")})},buildDayDragListener:function(){var a,b,c=this,d=this.view,e=d.opt("selectable"),f=new qb(this,{scroll:d.opt("dragScroll"),interactionStart:function(){a=f.origHit},dragStart:function(){d.unselect()},hitOver:function(d,f,h){h&&(f||(a=null),e&&(b=c.computeSelection(c.getHitSpan(h),c.getHitSpan(d)),b?c.renderSelection(b):b===!1&&g()))},hitOut:function(){a=null,b=null,c.unrenderSelection(),h()},interactionEnd:function(e,f){f||(a&&!c.isIgnoringMouse&&d.triggerDayClick(c.getHitSpan(a),c.getHitEl(a),e),b&&d.reportSelection(b,e),h())}});return f},clearDragListeners:function(){this.dayDragListener.endInteraction(),this.segDragListener&&this.segDragListener.endInteraction(),this.segResizeListener&&this.segResizeListener.endInteraction(),this.externalDragListener&&this.externalDragListener.endInteraction()},renderEventLocationHelper:function(a,b){var c=this.fabricateHelperEvent(a,b);return this.renderHelper(c,b)},fabricateHelperEvent:function(a,b){var c=b?X(b.event):{};return c.start=a.start.clone(),c.end=a.end?a.end.clone():null,c.allDay=null,this.view.calendar.normalizeEventDates(c),c.className=(c.className||[]).concat("fc-helper"),b||(c.editable=!1),c},renderHelper:function(a,b){},unrenderHelper:function(){},renderSelection:function(a){this.renderHighlight(a)},unrenderSelection:function(){this.unrenderHighlight()},computeSelection:function(a,b){var c=this.computeSelectionSpan(a,b);return c&&!this.view.calendar.isSelectionSpanAllowed(c)?!1:c},computeSelectionSpan:function(a,b){var c=[a.start,a.end,b.start,b.end];return c.sort(ga),{start:c[0].clone(),end:c[3].clone()}},renderHighlight:function(a){this.renderFill("highlight",this.spanToSegs(a))},unrenderHighlight:function(){this.unrenderFill("highlight")},highlightSegClasses:function(){return["fc-highlight"]},renderBusinessHours:function(){},unrenderBusinessHours:function(){},getNowIndicatorUnit:function(){},renderNowIndicator:function(a){},unrenderNowIndicator:function(){},renderFill:function(a,b){},unrenderFill:function(a){var b=this.elsByFill[a];b&&(b.remove(),delete this.elsByFill[a])},renderFillSegEls:function(b,c){var d,e=this,f=this[b+"SegEl"],g="",h=[];if(c.length){for(d=0;d<c.length;d++)g+=this.fillSegHtml(b,c[d]);a(g).each(function(b,d){var g=c[b],i=a(d);f&&(i=f.call(e,g,i)),i&&(i=a(i),i.is(e.fillSegTag)&&(g.el=i,h.push(g)))})}return h},fillSegTag:"div",fillSegHtml:function(a,b){var c=this[a+"SegClasses"],d=this[a+"SegCss"],e=c?c.call(this,b):[],f=ea(d?d.call(this,b):{});return"<"+this.fillSegTag+(e.length?' class="'+e.join(" ")+'"':"")+(f?' style="'+f+'"':"")+" />"},getDayClasses:function(a){var b=this.view,c=b.calendar.getNow(),d=["fc-"+Za[a.day()]];return 1==b.intervalDuration.as("months")&&a.month()!=b.intervalStart.month()&&d.push("fc-other-month"),a.isSame(c,"day")?d.push("fc-today",b.highlightStateClass):c>a?d.push("fc-past"):d.push("fc-future"),d}});sb.mixin({mousedOverSeg:null,isDraggingSeg:!1,isResizingSeg:!1,isDraggingExternal:!1,segs:null,renderEvents:function(a){var b,c=[],d=[];for(b=0;b<a.length;b++)(Ca(a[b])?c:d).push(a[b]);this.segs=[].concat(this.renderBgEvents(c),this.renderFgEvents(d))},renderBgEvents:function(a){var b=this.eventsToSegs(a);return this.renderBgSegs(b)||b},renderFgEvents:function(a){var b=this.eventsToSegs(a);return this.renderFgSegs(b)||b},unrenderEvents:function(){this.handleSegMouseout(),this.clearDragListeners(),this.unrenderFgSegs(),this.unrenderBgSegs(),this.segs=null},getEventSegs:function(){return this.segs||[]},renderFgSegs:function(a){},unrenderFgSegs:function(){},renderFgSegEls:function(b,c){var d,e=this.view,f="",g=[];if(b.length){for(d=0;d<b.length;d++)f+=this.fgSegHtml(b[d],c);a(f).each(function(c,d){var f=b[c],h=e.resolveEventEl(f.event,a(d));h&&(h.data("fc-seg",f),f.el=h,g.push(f))})}return g},fgSegHtml:function(a,b){},renderBgSegs:function(a){return this.renderFill("bgEvent",a)},unrenderBgSegs:function(){this.unrenderFill("bgEvent")},bgEventSegEl:function(a,b){return this.view.resolveEventEl(a.event,b)},bgEventSegClasses:function(a){var b=a.event,c=b.source||{};return["fc-bgevent"].concat(b.className,c.className||[])},bgEventSegCss:function(a){return{"background-color":this.getSegSkinCss(a)["background-color"]}},businessHoursSegClasses:function(a){return["fc-nonbusiness","fc-bgevent"]},bindSegHandlers:function(){this.bindSegHandler("touchstart",this.handleSegTouchStart),this.bindSegHandler("touchend",this.handleSegTouchEnd),this.bindSegHandler("mouseenter",this.handleSegMouseover),this.bindSegHandler("mouseleave",this.handleSegMouseout),this.bindSegHandler("mousedown",this.handleSegMousedown),this.bindSegHandler("click",this.handleSegClick)},bindSegHandler:function(b,c){var d=this;this.el.on(b,".fc-event-container > *",function(b){var e=a(this).data("fc-seg");return!e||d.isDraggingSeg||d.isResizingSeg?void 0:c.call(d,e,b)})},handleSegClick:function(a,b){return this.view.trigger("eventClick",a.el[0],a.event,b)},handleSegMouseover:function(a,b){this.isIgnoringMouse||this.mousedOverSeg||(this.mousedOverSeg=a,a.el.addClass("fc-allow-mouse-resize"),this.view.trigger("eventMouseover",a.el[0],a.event,b))},handleSegMouseout:function(a,b){b=b||{},this.mousedOverSeg&&(a=a||this.mousedOverSeg,this.mousedOverSeg=null,a.el.removeClass("fc-allow-mouse-resize"),this.view.trigger("eventMouseout",a.el[0],a.event,b))},handleSegMousedown:function(a,b){var c=this.startSegResize(a,b,{distance:5});!c&&this.view.isEventDraggable(a.event)&&this.buildSegDragListener(a).startInteraction(b,{distance:5})},handleSegTouchStart:function(a,b){var c,d=this.view,e=a.event,f=d.isEventSelected(e),g=d.isEventDraggable(e),h=d.isEventResizable(e),i=!1;f&&h&&(i=this.startSegResize(a,b)),i||!g&&!h||(c=g?this.buildSegDragListener(a):this.buildSegSelectListener(a),c.startInteraction(b,{delay:f?0:this.view.opt("longPressDelay")})),this.tempIgnoreMouse()},handleSegTouchEnd:function(a,b){this.tempIgnoreMouse()},startSegResize:function(b,c,d){return a(c.target).is(".fc-resizer")?(this.buildSegResizeListener(b,a(c.target).is(".fc-start-resizer")).startInteraction(c,d),!0):!1},buildSegDragListener:function(a){var b,c,d,e=this,f=this.view,i=f.calendar,j=a.el,k=a.event;if(this.segDragListener)return this.segDragListener;var l=this.segDragListener=new qb(f,{scroll:f.opt("dragScroll"),subjectEl:j,subjectCenter:!0,interactionStart:function(d){b=!1,c=new rb(a.el,{additionalClass:"fc-dragging",parentEl:f.el,opacity:l.isTouch?null:f.opt("dragOpacity"),revertDuration:f.opt("dragRevertDuration"),zIndex:2}),c.hide(),c.start(d)},dragStart:function(c){l.isTouch&&!f.isEventSelected(k)&&f.selectEvent(k),b=!0,e.handleSegMouseout(a,c),e.segDragStart(a,c),f.hideEvent(k)},hitOver:function(b,h,j){var m;a.hit&&(j=a.hit),d=e.computeEventDrop(j.component.getHitSpan(j),b.component.getHitSpan(b),k),d&&!i.isEventSpanAllowed(e.eventToSpan(d),k)&&(g(),d=null),d&&(m=f.renderDrag(d,a))?(m.addClass("fc-dragging"),l.isTouch||e.applyDragOpacity(m),c.hide()):c.show(),h&&(d=null)},hitOut:function(){f.unrenderDrag(),c.show(),d=null},hitDone:function(){h()},interactionEnd:function(g){c.stop(!d,function(){b&&(f.unrenderDrag(),f.showEvent(k),e.segDragStop(a,g)),d&&f.reportEventDrop(k,d,this.largeUnit,j,g)}),e.segDragListener=null}});return l},buildSegSelectListener:function(a){var b=this,c=this.view,d=a.event;if(this.segDragListener)return this.segDragListener;var e=this.segDragListener=new pb({dragStart:function(a){e.isTouch&&!c.isEventSelected(d)&&c.selectEvent(d)},interactionEnd:function(a){b.segDragListener=null}});return e},segDragStart:function(a,b){this.isDraggingSeg=!0,this.view.trigger("eventDragStart",a.el[0],a.event,b,{})},segDragStop:function(a,b){this.isDraggingSeg=!1,this.view.trigger("eventDragStop",a.el[0],a.event,b,{})},computeEventDrop:function(a,b,c){var d,e,f=this.view.calendar,g=a.start,h=b.start;return g.hasTime()===h.hasTime()?(d=this.diffDates(h,g),c.allDay&&T(d)?(e={start:c.start.clone(),end:f.getEventEnd(c),allDay:!1},f.normalizeEventTimes(e)):e={start:c.start.clone(),end:c.end?c.end.clone():null,allDay:c.allDay},e.start.add(d),e.end&&e.end.add(d)):e={start:h.clone(),end:null,allDay:!h.hasTime()},e},applyDragOpacity:function(a){var b=this.view.opt("dragOpacity");null!=b&&a.each(function(a,c){c.style.opacity=b})},externalDragStart:function(b,c){var d,e,f=this.view;f.opt("droppable")&&(d=a((c?c.item:null)||b.target),e=f.opt("dropAccept"),(a.isFunction(e)?e.call(d[0],d):d.is(e))&&(this.isDraggingExternal||this.listenToExternalDrag(d,b,c)))},listenToExternalDrag:function(a,b,c){var d,e=this,f=this.view.calendar,i=Ha(a),j=e.externalDragListener=new qb(this,{interactionStart:function(){e.isDraggingExternal=!0},hitOver:function(a){d=e.computeExternalDrop(a.component.getHitSpan(a),i),d&&!f.isExternalSpanAllowed(e.eventToSpan(d),d,i.eventProps)&&(g(),d=null),d&&e.renderDrag(d)},hitOut:function(){d=null},hitDone:function(){h(),e.unrenderDrag()},interactionEnd:function(b){d&&e.view.reportExternalDrop(i,d,a,b,c),e.isDraggingExternal=!1,e.externalDragListener=null}});j.startDrag(b)},computeExternalDrop:function(a,b){var c=this.view.calendar,d={start:c.applyTimezone(a.start),end:null};return b.startTime&&!d.start.hasTime()&&d.start.time(b.startTime),b.duration&&(d.end=d.start.clone().add(b.duration)),d},renderDrag:function(a,b){},unrenderDrag:function(){},buildSegResizeListener:function(a,b){var c,d,e=this,f=this.view,i=f.calendar,j=a.el,k=a.event,l=i.getEventEnd(k),m=this.segResizeListener=new qb(this,{scroll:f.opt("dragScroll"),subjectEl:j,interactionStart:function(){c=!1},dragStart:function(b){c=!0,e.handleSegMouseout(a,b),e.segResizeStart(a,b)},hitOver:function(c,h,j){var m=e.getHitSpan(j),n=e.getHitSpan(c);d=b?e.computeEventStartResize(m,n,k):e.computeEventEndResize(m,n,k),d&&(i.isEventSpanAllowed(e.eventToSpan(d),k)?d.start.isSame(k.start)&&d.end.isSame(l)&&(d=null):(g(),d=null)),d&&(f.hideEvent(k),e.renderEventResize(d,a))},hitOut:function(){d=null},hitDone:function(){e.unrenderEventResize(),f.showEvent(k),h()},interactionEnd:function(b){c&&e.segResizeStop(a,b),d&&f.reportEventResize(k,d,this.largeUnit,j,b),e.segResizeListener=null}});return m},segResizeStart:function(a,b){this.isResizingSeg=!0,this.view.trigger("eventResizeStart",a.el[0],a.event,b,{})},segResizeStop:function(a,b){this.isResizingSeg=!1,this.view.trigger("eventResizeStop",a.el[0],a.event,b,{})},computeEventStartResize:function(a,b,c){return this.computeEventResize("start",a,b,c)},computeEventEndResize:function(a,b,c){return this.computeEventResize("end",a,b,c)},computeEventResize:function(a,b,c,d){var e,f,g=this.view.calendar,h=this.diffDates(c[a],b[a]);return e={start:d.start.clone(),end:g.getEventEnd(d),allDay:d.allDay},e.allDay&&T(h)&&(e.allDay=!1,g.normalizeEventTimes(e)),e[a].add(h),e.start.isBefore(e.end)||(f=this.minResizeDuration||(d.allDay?g.defaultAllDayEventDuration:g.defaultTimedEventDuration),"start"==a?e.start=e.end.clone().subtract(f):e.end=e.start.clone().add(f)),e},renderEventResize:function(a,b){},unrenderEventResize:function(){},getEventTimeText:function(a,b,c){return null==b&&(b=this.eventTimeFormat),null==c&&(c=this.displayEventEnd),this.displayEventTime&&a.start.hasTime()?c&&a.end?this.view.formatRange(a,b):a.start.format(b):""},getSegClasses:function(a,b,c){var d=this.view,e=a.event,f=["fc-event",a.isStart?"fc-start":"fc-not-start",a.isEnd?"fc-end":"fc-not-end"].concat(e.className,e.source?e.source.className:[]);return b&&f.push("fc-draggable"),c&&f.push("fc-resizable"),d.isEventSelected(e)&&f.push("fc-selected"),f},getSegSkinCss:function(a){var b=a.event,c=this.view,d=b.source||{},e=b.color,f=d.color,g=c.opt("eventColor");return{"background-color":b.backgroundColor||e||d.backgroundColor||f||c.opt("eventBackgroundColor")||g,"border-color":b.borderColor||e||d.borderColor||f||c.opt("eventBorderColor")||g,color:b.textColor||d.textColor||c.opt("eventTextColor")}},eventToSegs:function(a){return this.eventsToSegs([a])},eventToSpan:function(a){return this.eventToSpans(a)[0]},eventToSpans:function(a){var b=this.eventToRange(a);return this.eventRangeToSpans(b,a)},eventsToSegs:function(b,c){var d=this,e=Fa(b),f=[];return a.each(e,function(a,b){var e,g=[];for(e=0;e<b.length;e++)g.push(d.eventToRange(b[e]));if(Da(b[0]))for(g=d.invertRanges(g),e=0;e<g.length;e++)f.push.apply(f,d.eventRangeToSegs(g[e],b[0],c));else for(e=0;e<g.length;e++)f.push.apply(f,d.eventRangeToSegs(g[e],b[e],c))}),f},eventToRange:function(a){return{start:a.start.clone().stripZone(),end:(a.end?a.end.clone():this.view.calendar.getDefaultEventEnd(null!=a.allDay?a.allDay:!a.start.hasTime(),a.start)).stripZone()}},eventRangeToSegs:function(a,b,c){var d,e=this.eventRangeToSpans(a,b),f=[];for(d=0;d<e.length;d++)f.push.apply(f,this.eventSpanToSegs(e[d],b,c));return f},eventRangeToSpans:function(b,c){return[a.extend({},b)]},eventSpanToSegs:function(a,b,c){var d,e,f=c?c(a):this.spanToSegs(a);for(d=0;d<f.length;d++)e=f[d],e.event=b,e.eventStartMS=+a.start,e.eventDurationMS=a.end-a.start;return f},invertRanges:function(a){var b,c,d=this.view,e=d.start.clone(),f=d.end.clone(),g=[],h=e;for(a.sort(Ga),b=0;b<a.length;b++)c=a[b],c.start>h&&g.push({start:h,end:c.start}),h=c.end;return f>h&&g.push({start:h,end:f}),g},sortEventSegs:function(a){a.sort(ia(this,"compareEventSegs"))},compareEventSegs:function(a,b){return a.eventStartMS-b.eventStartMS||b.eventDurationMS-a.eventDurationMS||b.event.allDay-a.event.allDay||H(a.event,b.event,this.view.eventOrderSpecs)}}),Va.isBgEvent=Ca,Va.dataAttrPrefix="";var tb=Va.DayTableMixin={breakOnWeeks:!1,dayDates:null,dayIndices:null,daysPerRow:null,rowCnt:null,colCnt:null,colHeadFormat:null,updateDayTable:function(){for(var a,b,c,d=this.view,e=this.start.clone(),f=-1,g=[],h=[];e.isBefore(this.end);)d.isHiddenDay(e)?g.push(f+.5):(f++,g.push(f),h.push(e.clone())),e.add(1,"days");if(this.breakOnWeeks){for(b=h[0].day(),a=1;a<h.length&&h[a].day()!=b;a++);c=Math.ceil(h.length/a)}else c=1,a=h.length;this.dayDates=h,this.dayIndices=g,this.daysPerRow=a,this.rowCnt=c,this.updateDayTableCols()},updateDayTableCols:function(){this.colCnt=this.computeColCnt(),this.colHeadFormat=this.view.opt("columnFormat")||this.computeColHeadFormat()},computeColCnt:function(){return this.daysPerRow},getCellDate:function(a,b){return this.dayDates[this.getCellDayIndex(a,b)].clone()},getCellRange:function(a,b){var c=this.getCellDate(a,b),d=c.clone().add(1,"days");return{start:c,end:d}},getCellDayIndex:function(a,b){return a*this.daysPerRow+this.getColDayIndex(b)},getColDayIndex:function(a){return this.isRTL?this.colCnt-1-a:a},getDateDayIndex:function(a){var b=this.dayIndices,c=a.diff(this.start,"days");return 0>c?b[0]-1:c>=b.length?b[b.length-1]+1:b[c]},computeColHeadFormat:function(){return this.rowCnt>1||this.colCnt>10?"ddd":this.colCnt>1?this.view.opt("dayOfMonthFormat"):"dddd"},sliceRangeByRow:function(a){var b,c,d,e,f,g=this.daysPerRow,h=this.view.computeDayRange(a),i=this.getDateDayIndex(h.start),j=this.getDateDayIndex(h.end.clone().subtract(1,"days")),k=[];for(b=0;b<this.rowCnt;b++)c=b*g,d=c+g-1,e=Math.max(i,c),f=Math.min(j,d),e=Math.ceil(e),f=Math.floor(f),f>=e&&k.push({row:b,firstRowDayIndex:e-c,lastRowDayIndex:f-c,isStart:e===i,isEnd:f===j});return k},sliceRangeByDay:function(a){var b,c,d,e,f,g,h=this.daysPerRow,i=this.view.computeDayRange(a),j=this.getDateDayIndex(i.start),k=this.getDateDayIndex(i.end.clone().subtract(1,"days")),l=[];for(b=0;b<this.rowCnt;b++)for(c=b*h,d=c+h-1,e=c;d>=e;e++)f=Math.max(j,e),g=Math.min(k,e),f=Math.ceil(f),g=Math.floor(g),g>=f&&l.push({row:b,firstRowDayIndex:f-c,lastRowDayIndex:g-c,isStart:f===j,isEnd:g===k});return l},renderHeadHtml:function(){var a=this.view;return'<div class="fc-row '+a.widgetHeaderClass+'"><table><thead>'+this.renderHeadTrHtml()+"</thead></table></div>"},renderHeadIntroHtml:function(){return this.renderIntroHtml()},renderHeadTrHtml:function(){return"<tr>"+(this.isRTL?"":this.renderHeadIntroHtml())+this.renderHeadDateCellsHtml()+(this.isRTL?this.renderHeadIntroHtml():"")+"</tr>"},renderHeadDateCellsHtml:function(){var a,b,c=[];for(a=0;a<this.colCnt;a++)b=this.getCellDate(0,a),c.push(this.renderHeadDateCellHtml(b));return c.join("")},renderHeadDateCellHtml:function(a,b,c){var d=this.view;return'<th class="fc-day-header '+d.widgetHeaderClass+" fc-"+Za[a.day()]+'"'+(1==this.rowCnt?' data-date="'+a.format("YYYY-MM-DD")+'"':"")+(b>1?' colspan="'+b+'"':"")+(c?" "+c:"")+">"+ca(a.format(this.colHeadFormat))+"</th>";
+},renderBgTrHtml:function(a){return"<tr>"+(this.isRTL?"":this.renderBgIntroHtml(a))+this.renderBgCellsHtml(a)+(this.isRTL?this.renderBgIntroHtml(a):"")+"</tr>"},renderBgIntroHtml:function(a){return this.renderIntroHtml()},renderBgCellsHtml:function(a){var b,c,d=[];for(b=0;b<this.colCnt;b++)c=this.getCellDate(a,b),d.push(this.renderBgCellHtml(c));return d.join("")},renderBgCellHtml:function(a,b){var c=this.view,d=this.getDayClasses(a);return d.unshift("fc-day",c.widgetContentClass),'<td class="'+d.join(" ")+'" data-date="'+a.format("YYYY-MM-DD")+'"'+(b?" "+b:"")+"></td>"},renderIntroHtml:function(){},bookendCells:function(a){var b=this.renderIntroHtml();b&&(this.isRTL?a.append(b):a.prepend(b))}},ub=Va.DayGrid=sb.extend(tb,{numbersVisible:!1,bottomCoordPadding:0,rowEls:null,cellEls:null,helperEls:null,rowCoordCache:null,colCoordCache:null,renderDates:function(a){var b,c,d=this.view,e=this.rowCnt,f=this.colCnt,g="";for(b=0;e>b;b++)g+=this.renderDayRowHtml(b,a);for(this.el.html(g),this.rowEls=this.el.find(".fc-row"),this.cellEls=this.el.find(".fc-day"),this.rowCoordCache=new ob({els:this.rowEls,isVertical:!0}),this.colCoordCache=new ob({els:this.cellEls.slice(0,this.colCnt),isHorizontal:!0}),b=0;e>b;b++)for(c=0;f>c;c++)d.trigger("dayRender",null,this.getCellDate(b,c),this.getCellEl(b,c))},unrenderDates:function(){this.removeSegPopover()},renderBusinessHours:function(){var a=this.view.calendar.getBusinessHoursEvents(!0),b=this.eventsToSegs(a);this.renderFill("businessHours",b,"bgevent")},renderDayRowHtml:function(a,b){var c=this.view,d=["fc-row","fc-week",c.widgetContentClass];return b&&d.push("fc-rigid"),'<div class="'+d.join(" ")+'"><div class="fc-bg"><table>'+this.renderBgTrHtml(a)+'</table></div><div class="fc-content-skeleton"><table>'+(this.numbersVisible?"<thead>"+this.renderNumberTrHtml(a)+"</thead>":"")+"</table></div></div>"},renderNumberTrHtml:function(a){return"<tr>"+(this.isRTL?"":this.renderNumberIntroHtml(a))+this.renderNumberCellsHtml(a)+(this.isRTL?this.renderNumberIntroHtml(a):"")+"</tr>"},renderNumberIntroHtml:function(a){return this.renderIntroHtml()},renderNumberCellsHtml:function(a){var b,c,d=[];for(b=0;b<this.colCnt;b++)c=this.getCellDate(a,b),d.push(this.renderNumberCellHtml(c));return d.join("")},renderNumberCellHtml:function(a){var b;return this.view.dayNumbersVisible?(b=this.getDayClasses(a),b.unshift("fc-day-number"),'<td class="'+b.join(" ")+'" data-date="'+a.format()+'">'+a.date()+"</td>"):"<td/>"},computeEventTimeFormat:function(){return this.view.opt("extraSmallTimeFormat")},computeDisplayEventEnd:function(){return 1==this.colCnt},rangeUpdated:function(){this.updateDayTable()},spanToSegs:function(a){var b,c,d=this.sliceRangeByRow(a);for(b=0;b<d.length;b++)c=d[b],this.isRTL?(c.leftCol=this.daysPerRow-1-c.lastRowDayIndex,c.rightCol=this.daysPerRow-1-c.firstRowDayIndex):(c.leftCol=c.firstRowDayIndex,c.rightCol=c.lastRowDayIndex);return d},prepareHits:function(){this.colCoordCache.build(),this.rowCoordCache.build(),this.rowCoordCache.bottoms[this.rowCnt-1]+=this.bottomCoordPadding},releaseHits:function(){this.colCoordCache.clear(),this.rowCoordCache.clear()},queryHit:function(a,b){var c=this.colCoordCache.getHorizontalIndex(a),d=this.rowCoordCache.getVerticalIndex(b);return null!=d&&null!=c?this.getCellHit(d,c):void 0},getHitSpan:function(a){return this.getCellRange(a.row,a.col)},getHitEl:function(a){return this.getCellEl(a.row,a.col)},getCellHit:function(a,b){return{row:a,col:b,component:this,left:this.colCoordCache.getLeftOffset(b),right:this.colCoordCache.getRightOffset(b),top:this.rowCoordCache.getTopOffset(a),bottom:this.rowCoordCache.getBottomOffset(a)}},getCellEl:function(a,b){return this.cellEls.eq(a*this.colCnt+b)},renderDrag:function(a,b){return this.renderHighlight(this.eventToSpan(a)),b&&!b.el.closest(this.el).length?this.renderEventLocationHelper(a,b):void 0},unrenderDrag:function(){this.unrenderHighlight(),this.unrenderHelper()},renderEventResize:function(a,b){return this.renderHighlight(this.eventToSpan(a)),this.renderEventLocationHelper(a,b)},unrenderEventResize:function(){this.unrenderHighlight(),this.unrenderHelper()},renderHelper:function(b,c){var d,e=[],f=this.eventToSegs(b);return f=this.renderFgSegEls(f),d=this.renderSegRows(f),this.rowEls.each(function(b,f){var g,h=a(f),i=a('<div class="fc-helper-skeleton"><table/></div>');g=c&&c.row===b?c.el.position().top:h.find(".fc-content-skeleton tbody").position().top,i.css("top",g).find("table").append(d[b].tbodyEl),h.append(i),e.push(i[0])}),this.helperEls=a(e)},unrenderHelper:function(){this.helperEls&&(this.helperEls.remove(),this.helperEls=null)},fillSegTag:"td",renderFill:function(b,c,d){var e,f,g,h=[];for(c=this.renderFillSegEls(b,c),e=0;e<c.length;e++)f=c[e],g=this.renderFillRow(b,f,d),this.rowEls.eq(f.row).append(g),h.push(g[0]);return this.elsByFill[b]=a(h),c},renderFillRow:function(b,c,d){var e,f,g=this.colCnt,h=c.leftCol,i=c.rightCol+1;return d=d||b.toLowerCase(),e=a('<div class="fc-'+d+'-skeleton"><table><tr/></table></div>'),f=e.find("tr"),h>0&&f.append('<td colspan="'+h+'"/>'),f.append(c.el.attr("colspan",i-h)),g>i&&f.append('<td colspan="'+(g-i)+'"/>'),this.bookendCells(f),e}});ub.mixin({rowStructs:null,unrenderEvents:function(){this.removeSegPopover(),sb.prototype.unrenderEvents.apply(this,arguments)},getEventSegs:function(){return sb.prototype.getEventSegs.call(this).concat(this.popoverSegs||[])},renderBgSegs:function(b){var c=a.grep(b,function(a){return a.event.allDay});return sb.prototype.renderBgSegs.call(this,c)},renderFgSegs:function(b){var c;return b=this.renderFgSegEls(b),c=this.rowStructs=this.renderSegRows(b),this.rowEls.each(function(b,d){a(d).find(".fc-content-skeleton > table").append(c[b].tbodyEl)}),b},unrenderFgSegs:function(){for(var a,b=this.rowStructs||[];a=b.pop();)a.tbodyEl.remove();this.rowStructs=null},renderSegRows:function(a){var b,c,d=[];for(b=this.groupSegRows(a),c=0;c<b.length;c++)d.push(this.renderSegRow(c,b[c]));return d},fgSegHtml:function(a,b){var c,d,e=this.view,f=a.event,g=e.isEventDraggable(f),h=!b&&f.allDay&&a.isStart&&e.isEventResizableFromStart(f),i=!b&&f.allDay&&a.isEnd&&e.isEventResizableFromEnd(f),j=this.getSegClasses(a,g,h||i),k=ea(this.getSegSkinCss(a)),l="";return j.unshift("fc-day-grid-event","fc-h-event"),a.isStart&&(c=this.getEventTimeText(f),c&&(l='<span class="fc-time">'+ca(c)+"</span>")),d='<span class="fc-title">'+(ca(f.title||"")||"&nbsp;")+"</span>",'<a class="'+j.join(" ")+'"'+(f.url?' href="'+ca(f.url)+'"':"")+(k?' style="'+k+'"':"")+'><div class="fc-content">'+(this.isRTL?d+" "+l:l+" "+d)+"</div>"+(h?'<div class="fc-resizer fc-start-resizer" />':"")+(i?'<div class="fc-resizer fc-end-resizer" />':"")+"</a>"},renderSegRow:function(b,c){function d(b){for(;b>g;)k=(r[e-1]||[])[g],k?k.attr("rowspan",parseInt(k.attr("rowspan")||1,10)+1):(k=a("<td/>"),h.append(k)),q[e][g]=k,r[e][g]=k,g++}var e,f,g,h,i,j,k,l=this.colCnt,m=this.buildSegLevels(c),n=Math.max(1,m.length),o=a("<tbody/>"),p=[],q=[],r=[];for(e=0;n>e;e++){if(f=m[e],g=0,h=a("<tr/>"),p.push([]),q.push([]),r.push([]),f)for(i=0;i<f.length;i++){for(j=f[i],d(j.leftCol),k=a('<td class="fc-event-container"/>').append(j.el),j.leftCol!=j.rightCol?k.attr("colspan",j.rightCol-j.leftCol+1):r[e][g]=k;g<=j.rightCol;)q[e][g]=k,p[e][g]=j,g++;h.append(k)}d(l),this.bookendCells(h),o.append(h)}return{row:b,tbodyEl:o,cellMatrix:q,segMatrix:p,segLevels:m,segs:c}},buildSegLevels:function(a){var b,c,d,e=[];for(this.sortEventSegs(a),b=0;b<a.length;b++){for(c=a[b],d=0;d<e.length&&Ia(c,e[d]);d++);c.level=d,(e[d]||(e[d]=[])).push(c)}for(d=0;d<e.length;d++)e[d].sort(Ja);return e},groupSegRows:function(a){var b,c=[];for(b=0;b<this.rowCnt;b++)c.push([]);for(b=0;b<a.length;b++)c[a[b].row].push(a[b]);return c}}),ub.mixin({segPopover:null,popoverSegs:null,removeSegPopover:function(){this.segPopover&&this.segPopover.hide()},limitRows:function(a){var b,c,d=this.rowStructs||[];for(b=0;b<d.length;b++)this.unlimitRow(b),c=a?"number"==typeof a?a:this.computeRowLevelLimit(b):!1,c!==!1&&this.limitRow(b,c)},computeRowLevelLimit:function(b){function c(b,c){f=Math.max(f,a(c).outerHeight())}var d,e,f,g=this.rowEls.eq(b),h=g.height(),i=this.rowStructs[b].tbodyEl.children();for(d=0;d<i.length;d++)if(e=i.eq(d).removeClass("fc-limited"),f=0,e.find("> td > :first-child").each(c),e.position().top+f>h)return d;return!1},limitRow:function(b,c){function d(d){for(;d>w;)j=t.getCellSegs(b,w,c),j.length&&(m=f[c-1][w],s=t.renderMoreLink(b,w,j),r=a("<div/>").append(s),m.append(r),v.push(r[0])),w++}var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t=this,u=this.rowStructs[b],v=[],w=0;if(c&&c<u.segLevels.length){for(e=u.segLevels[c-1],f=u.cellMatrix,g=u.tbodyEl.children().slice(c).addClass("fc-limited").get(),h=0;h<e.length;h++){for(i=e[h],d(i.leftCol),l=[],k=0;w<=i.rightCol;)j=this.getCellSegs(b,w,c),l.push(j),k+=j.length,w++;if(k){for(m=f[c-1][i.leftCol],n=m.attr("rowspan")||1,o=[],p=0;p<l.length;p++)q=a('<td class="fc-more-cell"/>').attr("rowspan",n),j=l[p],s=this.renderMoreLink(b,i.leftCol+p,[i].concat(j)),r=a("<div/>").append(s),q.append(r),o.push(q[0]),v.push(q[0]);m.addClass("fc-limited").after(a(o)),g.push(m[0])}}d(this.colCnt),u.moreEls=a(v),u.limitedEls=a(g)}},unlimitRow:function(a){var b=this.rowStructs[a];b.moreEls&&(b.moreEls.remove(),b.moreEls=null),b.limitedEls&&(b.limitedEls.removeClass("fc-limited"),b.limitedEls=null)},renderMoreLink:function(b,c,d){var e=this,f=this.view;return a('<a class="fc-more"/>').text(this.getMoreLinkText(d.length)).on("click",function(g){var h=f.opt("eventLimitClick"),i=e.getCellDate(b,c),j=a(this),k=e.getCellEl(b,c),l=e.getCellSegs(b,c),m=e.resliceDaySegs(l,i),n=e.resliceDaySegs(d,i);"function"==typeof h&&(h=f.trigger("eventLimitClick",null,{date:i,dayEl:k,moreEl:j,segs:m,hiddenSegs:n},g)),"popover"===h?e.showSegPopover(b,c,j,m):"string"==typeof h&&f.calendar.zoomTo(i,h)})},showSegPopover:function(a,b,c,d){var e,f,g=this,h=this.view,i=c.parent();e=1==this.rowCnt?h.el:this.rowEls.eq(a),f={className:"fc-more-popover",content:this.renderSegPopoverContent(a,b,d),parentEl:this.el,top:e.offset().top,autoHide:!0,viewportConstrain:h.opt("popoverViewportConstrain"),hide:function(){g.segPopover.removeElement(),g.segPopover=null,g.popoverSegs=null}},this.isRTL?f.right=i.offset().left+i.outerWidth()+1:f.left=i.offset().left-1,this.segPopover=new nb(f),this.segPopover.show()},renderSegPopoverContent:function(b,c,d){var e,f=this.view,g=f.opt("theme"),h=this.getCellDate(b,c).format(f.opt("dayPopoverFormat")),i=a('<div class="fc-header '+f.widgetHeaderClass+'"><span class="fc-close '+(g?"ui-icon ui-icon-closethick":"fc-icon fc-icon-x")+'"></span><span class="fc-title">'+ca(h)+'</span><div class="fc-clear"/></div><div class="fc-body '+f.widgetContentClass+'"><div class="fc-event-container"></div></div>'),j=i.find(".fc-event-container");for(d=this.renderFgSegEls(d,!0),this.popoverSegs=d,e=0;e<d.length;e++)this.prepareHits(),d[e].hit=this.getCellHit(b,c),this.releaseHits(),j.append(d[e].el);return i},resliceDaySegs:function(b,c){var d=a.map(b,function(a){return a.event}),e=c.clone(),f=e.clone().add(1,"days"),g={start:e,end:f};return b=this.eventsToSegs(d,function(a){var b=K(a,g);return b?[b]:[]}),this.sortEventSegs(b),b},getMoreLinkText:function(a){var b=this.view.opt("eventLimitText");return"function"==typeof b?b(a):"+"+a+" "+b},getCellSegs:function(a,b,c){for(var d,e=this.rowStructs[a].segMatrix,f=c||0,g=[];f<e.length;)d=e[f][b],d&&g.push(d),f++;return g}});var vb=Va.TimeGrid=sb.extend(tb,{slotDuration:null,snapDuration:null,snapsPerSlot:null,minTime:null,maxTime:null,labelFormat:null,labelInterval:null,colEls:null,slatContainerEl:null,slatEls:null,nowIndicatorEls:null,colCoordCache:null,slatCoordCache:null,constructor:function(){sb.apply(this,arguments),this.processOptions()},renderDates:function(){this.el.html(this.renderHtml()),this.colEls=this.el.find(".fc-day"),this.slatContainerEl=this.el.find(".fc-slats"),this.slatEls=this.slatContainerEl.find("tr"),this.colCoordCache=new ob({els:this.colEls,isHorizontal:!0}),this.slatCoordCache=new ob({els:this.slatEls,isVertical:!0}),this.renderContentSkeleton()},renderHtml:function(){return'<div class="fc-bg"><table>'+this.renderBgTrHtml(0)+'</table></div><div class="fc-slats"><table>'+this.renderSlatRowHtml()+"</table></div>"},renderSlatRowHtml:function(){for(var a,c,d,e=this.view,f=this.isRTL,g="",h=b.duration(+this.minTime);h<this.maxTime;)a=this.start.clone().time(h),c=ha(R(h,this.labelInterval)),d='<td class="fc-axis fc-time '+e.widgetContentClass+'" '+e.axisStyleAttr()+">"+(c?"<span>"+ca(a.format(this.labelFormat))+"</span>":"")+"</td>",g+='<tr data-time="'+a.format("HH:mm:ss")+'"'+(c?"":' class="fc-minor"')+">"+(f?"":d)+'<td class="'+e.widgetContentClass+'"/>'+(f?d:"")+"</tr>",h.add(this.slotDuration);return g},processOptions:function(){var c,d=this.view,e=d.opt("slotDuration"),f=d.opt("snapDuration");e=b.duration(e),f=f?b.duration(f):e,this.slotDuration=e,this.snapDuration=f,this.snapsPerSlot=e/f,this.minResizeDuration=f,this.minTime=b.duration(d.opt("minTime")),this.maxTime=b.duration(d.opt("maxTime")),c=d.opt("slotLabelFormat"),a.isArray(c)&&(c=c[c.length-1]),this.labelFormat=c||d.opt("axisFormat")||d.opt("smallTimeFormat"),c=d.opt("slotLabelInterval"),this.labelInterval=c?b.duration(c):this.computeLabelInterval(e)},computeLabelInterval:function(a){var c,d,e;for(c=Mb.length-1;c>=0;c--)if(d=b.duration(Mb[c]),e=R(d,a),ha(e)&&e>1)return d;return b.duration(a)},computeEventTimeFormat:function(){return this.view.opt("noMeridiemTimeFormat")},computeDisplayEventEnd:function(){return!0},prepareHits:function(){this.colCoordCache.build(),this.slatCoordCache.build()},releaseHits:function(){this.colCoordCache.clear()},queryHit:function(a,b){var c=this.snapsPerSlot,d=this.colCoordCache,e=this.slatCoordCache,f=d.getHorizontalIndex(a),g=e.getVerticalIndex(b);if(null!=f&&null!=g){var h=e.getTopOffset(g),i=e.getHeight(g),j=(b-h)/i,k=Math.floor(j*c),l=g*c+k,m=h+k/c*i,n=h+(k+1)/c*i;return{col:f,snap:l,component:this,left:d.getLeftOffset(f),right:d.getRightOffset(f),top:m,bottom:n}}},getHitSpan:function(a){var b,c=this.getCellDate(0,a.col),d=this.computeSnapTime(a.snap);return c.time(d),b=c.clone().add(this.snapDuration),{start:c,end:b}},getHitEl:function(a){return this.colEls.eq(a.col)},rangeUpdated:function(){this.updateDayTable()},computeSnapTime:function(a){return b.duration(this.minTime+this.snapDuration*a)},spanToSegs:function(a){var b,c=this.sliceRangeByTimes(a);for(b=0;b<c.length;b++)this.isRTL?c[b].col=this.daysPerRow-1-c[b].dayIndex:c[b].col=c[b].dayIndex;return c},sliceRangeByTimes:function(a){var b,c,d,e,f=[];for(c=0;c<this.daysPerRow;c++)d=this.dayDates[c].clone(),e={start:d.clone().time(this.minTime),end:d.clone().time(this.maxTime)},b=K(a,e),b&&(b.dayIndex=c,f.push(b));return f},updateSize:function(a){this.slatCoordCache.build(),a&&this.updateSegVerticals([].concat(this.fgSegs||[],this.bgSegs||[],this.businessSegs||[]))},getTotalSlatHeight:function(){return this.slatContainerEl.outerHeight()},computeDateTop:function(a,c){return this.computeTimeTop(b.duration(a-c.clone().stripTime()))},computeTimeTop:function(a){var b,c,d=this.slatEls.length,e=(a-this.minTime)/this.slotDuration;return e=Math.max(0,e),e=Math.min(d,e),b=Math.floor(e),b=Math.min(b,d-1),c=e-b,this.slatCoordCache.getTopPosition(b)+this.slatCoordCache.getHeight(b)*c},renderDrag:function(a,b){return b?this.renderEventLocationHelper(a,b):void this.renderHighlight(this.eventToSpan(a))},unrenderDrag:function(){this.unrenderHelper(),this.unrenderHighlight()},renderEventResize:function(a,b){return this.renderEventLocationHelper(a,b)},unrenderEventResize:function(){this.unrenderHelper()},renderHelper:function(a,b){return this.renderHelperSegs(this.eventToSegs(a),b)},unrenderHelper:function(){this.unrenderHelperSegs()},renderBusinessHours:function(){var a=this.view.calendar.getBusinessHoursEvents(),b=this.eventsToSegs(a);this.renderBusinessSegs(b)},unrenderBusinessHours:function(){this.unrenderBusinessSegs()},getNowIndicatorUnit:function(){return"minute"},renderNowIndicator:function(b){var c,d=this.spanToSegs({start:b,end:b}),e=this.computeDateTop(b,b),f=[];for(c=0;c<d.length;c++)f.push(a('<div class="fc-now-indicator fc-now-indicator-line"></div>').css("top",e).appendTo(this.colContainerEls.eq(d[c].col))[0]);d.length>0&&f.push(a('<div class="fc-now-indicator fc-now-indicator-arrow"></div>').css("top",e).appendTo(this.el.find(".fc-content-skeleton"))[0]),this.nowIndicatorEls=a(f)},unrenderNowIndicator:function(){this.nowIndicatorEls&&(this.nowIndicatorEls.remove(),this.nowIndicatorEls=null)},renderSelection:function(a){this.view.opt("selectHelper")?this.renderEventLocationHelper(a):this.renderHighlight(a)},unrenderSelection:function(){this.unrenderHelper(),this.unrenderHighlight()},renderHighlight:function(a){this.renderHighlightSegs(this.spanToSegs(a))},unrenderHighlight:function(){this.unrenderHighlightSegs()}});vb.mixin({colContainerEls:null,fgContainerEls:null,bgContainerEls:null,helperContainerEls:null,highlightContainerEls:null,businessContainerEls:null,fgSegs:null,bgSegs:null,helperSegs:null,highlightSegs:null,businessSegs:null,renderContentSkeleton:function(){var b,c,d="";for(b=0;b<this.colCnt;b++)d+='<td><div class="fc-content-col"><div class="fc-event-container fc-helper-container"></div><div class="fc-event-container"></div><div class="fc-highlight-container"></div><div class="fc-bgevent-container"></div><div class="fc-business-container"></div></div></td>';c=a('<div class="fc-content-skeleton"><table><tr>'+d+"</tr></table></div>"),this.colContainerEls=c.find(".fc-content-col"),this.helperContainerEls=c.find(".fc-helper-container"),this.fgContainerEls=c.find(".fc-event-container:not(.fc-helper-container)"),this.bgContainerEls=c.find(".fc-bgevent-container"),this.highlightContainerEls=c.find(".fc-highlight-container"),this.businessContainerEls=c.find(".fc-business-container"),this.bookendCells(c.find("tr")),this.el.append(c)},renderFgSegs:function(a){return a=this.renderFgSegsIntoContainers(a,this.fgContainerEls),this.fgSegs=a,a},unrenderFgSegs:function(){this.unrenderNamedSegs("fgSegs")},renderHelperSegs:function(b,c){var d,e,f,g=[];for(b=this.renderFgSegsIntoContainers(b,this.helperContainerEls),d=0;d<b.length;d++)e=b[d],c&&c.col===e.col&&(f=c.el,e.el.css({left:f.css("left"),right:f.css("right"),"margin-left":f.css("margin-left"),"margin-right":f.css("margin-right")})),g.push(e.el[0]);return this.helperSegs=b,a(g)},unrenderHelperSegs:function(){this.unrenderNamedSegs("helperSegs")},renderBgSegs:function(a){return a=this.renderFillSegEls("bgEvent",a),this.updateSegVerticals(a),this.attachSegsByCol(this.groupSegsByCol(a),this.bgContainerEls),this.bgSegs=a,a},unrenderBgSegs:function(){this.unrenderNamedSegs("bgSegs")},renderHighlightSegs:function(a){a=this.renderFillSegEls("highlight",a),this.updateSegVerticals(a),this.attachSegsByCol(this.groupSegsByCol(a),this.highlightContainerEls),this.highlightSegs=a},unrenderHighlightSegs:function(){this.unrenderNamedSegs("highlightSegs")},renderBusinessSegs:function(a){a=this.renderFillSegEls("businessHours",a),this.updateSegVerticals(a),this.attachSegsByCol(this.groupSegsByCol(a),this.businessContainerEls),this.businessSegs=a},unrenderBusinessSegs:function(){this.unrenderNamedSegs("businessSegs")},groupSegsByCol:function(a){var b,c=[];for(b=0;b<this.colCnt;b++)c.push([]);for(b=0;b<a.length;b++)c[a[b].col].push(a[b]);return c},attachSegsByCol:function(a,b){var c,d,e;for(c=0;c<this.colCnt;c++)for(d=a[c],e=0;e<d.length;e++)b.eq(c).append(d[e].el)},unrenderNamedSegs:function(a){var b,c=this[a];if(c){for(b=0;b<c.length;b++)c[b].el.remove();this[a]=null}},renderFgSegsIntoContainers:function(a,b){var c,d;for(a=this.renderFgSegEls(a),c=this.groupSegsByCol(a),d=0;d<this.colCnt;d++)this.updateFgSegCoords(c[d]);return this.attachSegsByCol(c,b),a},fgSegHtml:function(a,b){var c,d,e,f=this.view,g=a.event,h=f.isEventDraggable(g),i=!b&&a.isStart&&f.isEventResizableFromStart(g),j=!b&&a.isEnd&&f.isEventResizableFromEnd(g),k=this.getSegClasses(a,h,i||j),l=ea(this.getSegSkinCss(a));return k.unshift("fc-time-grid-event","fc-v-event"),f.isMultiDayEvent(g)?(a.isStart||a.isEnd)&&(c=this.getEventTimeText(a),d=this.getEventTimeText(a,"LT"),e=this.getEventTimeText(a,null,!1)):(c=this.getEventTimeText(g),d=this.getEventTimeText(g,"LT"),e=this.getEventTimeText(g,null,!1)),'<a class="'+k.join(" ")+'"'+(g.url?' href="'+ca(g.url)+'"':"")+(l?' style="'+l+'"':"")+'><div class="fc-content">'+(c?'<div class="fc-time" data-start="'+ca(e)+'" data-full="'+ca(d)+'"><span>'+ca(c)+"</span></div>":"")+(g.title?'<div class="fc-title">'+ca(g.title)+"</div>":"")+'</div><div class="fc-bg"/>'+(j?'<div class="fc-resizer fc-end-resizer" />':"")+"</a>"},updateSegVerticals:function(a){this.computeSegVerticals(a),this.assignSegVerticals(a)},computeSegVerticals:function(a){var b,c;for(b=0;b<a.length;b++)c=a[b],c.top=this.computeDateTop(c.start,c.start),c.bottom=this.computeDateTop(c.end,c.start)},assignSegVerticals:function(a){var b,c;for(b=0;b<a.length;b++)c=a[b],c.el.css(this.generateSegVerticalCss(c))},generateSegVerticalCss:function(a){return{top:a.top,bottom:-a.bottom}},updateFgSegCoords:function(a){this.computeSegVerticals(a),this.computeFgSegHorizontals(a),this.assignSegVerticals(a),this.assignFgSegHorizontals(a)},computeFgSegHorizontals:function(a){var b,c,d;if(this.sortEventSegs(a),b=Ka(a),La(b),c=b[0]){for(d=0;d<c.length;d++)Ma(c[d]);for(d=0;d<c.length;d++)this.computeFgSegForwardBack(c[d],0,0)}},computeFgSegForwardBack:function(a,b,c){var d,e=a.forwardSegs;if(void 0===a.forwardCoord)for(e.length?(this.sortForwardSegs(e),this.computeFgSegForwardBack(e[0],b+1,c),a.forwardCoord=e[0].backwardCoord):a.forwardCoord=1,a.backwardCoord=a.forwardCoord-(a.forwardCoord-c)/(b+1),d=0;d<e.length;d++)this.computeFgSegForwardBack(e[d],0,a.forwardCoord)},sortForwardSegs:function(a){a.sort(ia(this,"compareForwardSegs"))},compareForwardSegs:function(a,b){return b.forwardPressure-a.forwardPressure||(a.backwardCoord||0)-(b.backwardCoord||0)||this.compareEventSegs(a,b)},assignFgSegHorizontals:function(a){var b,c;for(b=0;b<a.length;b++)c=a[b],c.el.css(this.generateFgSegHorizontalCss(c)),c.bottom-c.top<30&&c.el.addClass("fc-short")},generateFgSegHorizontalCss:function(a){var b,c,d=this.view.opt("slotEventOverlap"),e=a.backwardCoord,f=a.forwardCoord,g=this.generateSegVerticalCss(a);return d&&(f=Math.min(1,e+2*(f-e))),this.isRTL?(b=1-f,c=e):(b=e,c=1-f),g.zIndex=a.level+1,g.left=100*b+"%",g.right=100*c+"%",d&&a.forwardPressure&&(g[this.isRTL?"marginLeft":"marginRight"]=20),g}});var wb=Va.View=xa.extend(kb,lb,{type:null,name:null,title:null,calendar:null,options:null,el:null,displaying:null,isSkeletonRendered:!1,isEventsRendered:!1,start:null,end:null,intervalStart:null,intervalEnd:null,intervalDuration:null,intervalUnit:null,isRTL:!1,isSelected:!1,selectedEvent:null,eventOrderSpecs:null,widgetHeaderClass:null,widgetContentClass:null,highlightStateClass:null,nextDayThreshold:null,isHiddenDayHash:null,isNowIndicatorRendered:null,initialNowDate:null,initialNowQueriedMs:null,nowIndicatorTimeoutID:null,nowIndicatorIntervalID:null,constructor:function(a,c,d,e){this.calendar=a,this.type=this.name=c,this.options=d,this.intervalDuration=e||b.duration(1,"day"),this.nextDayThreshold=b.duration(this.opt("nextDayThreshold")),this.initThemingProps(),this.initHiddenDays(),this.isRTL=this.opt("isRTL"),this.eventOrderSpecs=G(this.opt("eventOrder")),this.initialize()},initialize:function(){},opt:function(a){return this.options[a]},trigger:function(a,b){var c=this.calendar;return c.trigger.apply(c,[a,b||this].concat(Array.prototype.slice.call(arguments,2),[this]))},setDate:function(a){this.setRange(this.computeRange(a))},setRange:function(b){a.extend(this,b),this.updateTitle()},computeRange:function(a){var b,c,d=O(this.intervalDuration),e=a.clone().startOf(d),f=e.clone().add(this.intervalDuration);return/year|month|week|day/.test(d)?(e.stripTime(),f.stripTime()):(e.hasTime()||(e=this.calendar.time(0)),f.hasTime()||(f=this.calendar.time(0))),b=e.clone(),b=this.skipHiddenDays(b),c=f.clone(),c=this.skipHiddenDays(c,-1,!0),{intervalUnit:d,intervalStart:e,intervalEnd:f,start:b,end:c}},computePrevDate:function(a){return this.massageCurrentDate(a.clone().startOf(this.intervalUnit).subtract(this.intervalDuration),-1)},computeNextDate:function(a){return this.massageCurrentDate(a.clone().startOf(this.intervalUnit).add(this.intervalDuration))},massageCurrentDate:function(a,b){return this.intervalDuration.as("days")<=1&&this.isHiddenDay(a)&&(a=this.skipHiddenDays(a,b),a.startOf("day")),a},updateTitle:function(){this.title=this.computeTitle()},computeTitle:function(){return this.formatRange({start:this.calendar.applyTimezone(this.intervalStart),end:this.calendar.applyTimezone(this.intervalEnd)},this.opt("titleFormat")||this.computeTitleFormat(),this.opt("titleRangeSeparator"))},computeTitleFormat:function(){return"year"==this.intervalUnit?"YYYY":"month"==this.intervalUnit?this.opt("monthYearFormat"):this.intervalDuration.as("days")>1?"ll":"LL"},formatRange:function(a,b,c){var d=a.end;return d.hasTime()||(d=d.clone().subtract(1)),sa(a.start,d,b,c,this.opt("isRTL"))},setElement:function(a){this.el=a,this.bindGlobalHandlers()},removeElement:function(){this.clear(),this.isSkeletonRendered&&(this.unrenderSkeleton(),this.isSkeletonRendered=!1),this.unbindGlobalHandlers(),this.el.remove()},display:function(b){var c=this,d=null;return this.displaying&&(d=this.queryScroll()),this.calendar.freezeContentHeight(),this.clear().then(function(){return c.displaying=a.when(c.displayView(b)).then(function(){c.forceScroll(c.computeInitialScroll(d)),c.calendar.unfreezeContentHeight(),c.triggerRender()})})},clear:function(){var b=this,c=this.displaying;return c?c.then(function(){return b.displaying=null,b.clearEvents(),b.clearView()}):a.when()},displayView:function(a){this.isSkeletonRendered||(this.renderSkeleton(),this.isSkeletonRendered=!0),a&&this.setDate(a),this.render&&this.render(),this.renderDates(),this.updateSize(),this.renderBusinessHours(),this.startNowIndicator()},clearView:function(){this.unselect(),this.stopNowIndicator(),this.triggerUnrender(),this.unrenderBusinessHours(),this.unrenderDates(),this.destroy&&this.destroy()},renderSkeleton:function(){},unrenderSkeleton:function(){},renderDates:function(){},unrenderDates:function(){},triggerRender:function(){this.trigger("viewRender",this,this,this.el)},triggerUnrender:function(){this.trigger("viewDestroy",this,this,this.el)},bindGlobalHandlers:function(){this.listenTo(a(document),"mousedown",this.handleDocumentMousedown),this.listenTo(a(document),"touchstart",this.processUnselect)},unbindGlobalHandlers:function(){this.stopListeningTo(a(document))},initThemingProps:function(){var a=this.opt("theme")?"ui":"fc";this.widgetHeaderClass=a+"-widget-header",this.widgetContentClass=a+"-widget-content",this.highlightStateClass=a+"-state-highlight"},renderBusinessHours:function(){},unrenderBusinessHours:function(){},startNowIndicator:function(){var a,c,d,e=this;this.opt("nowIndicator")&&(a=this.getNowIndicatorUnit(),a&&(c=ia(this,"updateNowIndicator"),this.initialNowDate=this.calendar.getNow(),this.initialNowQueriedMs=+new Date,this.renderNowIndicator(this.initialNowDate),this.isNowIndicatorRendered=!0,d=this.initialNowDate.clone().startOf(a).add(1,a)-this.initialNowDate,this.nowIndicatorTimeoutID=setTimeout(function(){e.nowIndicatorTimeoutID=null,c(),d=+b.duration(1,a),d=Math.max(100,d),e.nowIndicatorIntervalID=setInterval(c,d)},d)))},updateNowIndicator:function(){this.isNowIndicatorRendered&&(this.unrenderNowIndicator(),this.renderNowIndicator(this.initialNowDate.clone().add(new Date-this.initialNowQueriedMs)))},stopNowIndicator:function(){this.isNowIndicatorRendered&&(this.nowIndicatorTimeoutID&&(clearTimeout(this.nowIndicatorTimeoutID),this.nowIndicatorTimeoutID=null),this.nowIndicatorIntervalID&&(clearTimeout(this.nowIndicatorIntervalID),this.nowIndicatorIntervalID=null),this.unrenderNowIndicator(),this.isNowIndicatorRendered=!1)},getNowIndicatorUnit:function(){},renderNowIndicator:function(a){},unrenderNowIndicator:function(){},updateSize:function(a){var b;a&&(b=this.queryScroll()),this.updateHeight(a),this.updateWidth(a),this.updateNowIndicator(),a&&this.setScroll(b)},updateWidth:function(a){},updateHeight:function(a){var b=this.calendar;this.setHeight(b.getSuggestedViewHeight(),b.isHeightAuto())},setHeight:function(a,b){},computeInitialScroll:function(a){return 0},queryScroll:function(){},setScroll:function(a){},forceScroll:function(a){var b=this;this.setScroll(a),setTimeout(function(){b.setScroll(a)},0)},displayEvents:function(a){var b=this.queryScroll();this.clearEvents(),this.renderEvents(a),this.isEventsRendered=!0,this.setScroll(b),this.triggerEventRender()},clearEvents:function(){var a;this.isEventsRendered&&(a=this.queryScroll(),this.triggerEventUnrender(),this.destroyEvents&&this.destroyEvents(),this.unrenderEvents(),this.setScroll(a),this.isEventsRendered=!1)},renderEvents:function(a){},unrenderEvents:function(){},triggerEventRender:function(){this.renderedEventSegEach(function(a){this.trigger("eventAfterRender",a.event,a.event,a.el)}),this.trigger("eventAfterAllRender")},triggerEventUnrender:function(){this.renderedEventSegEach(function(a){this.trigger("eventDestroy",a.event,a.event,a.el)})},resolveEventEl:function(b,c){var d=this.trigger("eventRender",b,b,c);return d===!1?c=null:d&&d!==!0&&(c=a(d)),c},showEvent:function(a){this.renderedEventSegEach(function(a){a.el.css("visibility","")},a)},hideEvent:function(a){this.renderedEventSegEach(function(a){a.el.css("visibility","hidden")},a)},renderedEventSegEach:function(a,b){var c,d=this.getEventSegs();for(c=0;c<d.length;c++)b&&d[c].event._id!==b._id||d[c].el&&a.call(this,d[c])},getEventSegs:function(){return[]},isEventDraggable:function(a){var b=a.source||{};return ba(a.startEditable,b.startEditable,this.opt("eventStartEditable"),a.editable,b.editable,this.opt("editable"))},reportEventDrop:function(a,b,c,d,e){var f=this.calendar,g=f.mutateEvent(a,b,c),h=function(){g.undo(),f.reportEventChange()};this.triggerEventDrop(a,g.dateDelta,h,d,e),f.reportEventChange()},triggerEventDrop:function(a,b,c,d,e){this.trigger("eventDrop",d[0],a,b,c,e,{})},reportExternalDrop:function(b,c,d,e,f){var g,h,i=b.eventProps;i&&(g=a.extend({},i,c),h=this.calendar.renderEvent(g,b.stick)[0]),this.triggerExternalDrop(h,c,d,e,f)},triggerExternalDrop:function(a,b,c,d,e){this.trigger("drop",c[0],b.start,d,e),a&&this.trigger("eventReceive",null,a)},renderDrag:function(a,b){},unrenderDrag:function(){},isEventResizableFromStart:function(a){return this.opt("eventResizableFromStart")&&this.isEventResizable(a)},isEventResizableFromEnd:function(a){return this.isEventResizable(a)},isEventResizable:function(a){var b=a.source||{};return ba(a.durationEditable,b.durationEditable,this.opt("eventDurationEditable"),a.editable,b.editable,this.opt("editable"))},reportEventResize:function(a,b,c,d,e){var f=this.calendar,g=f.mutateEvent(a,b,c),h=function(){g.undo(),f.reportEventChange()};this.triggerEventResize(a,g.durationDelta,h,d,e),f.reportEventChange()},triggerEventResize:function(a,b,c,d,e){this.trigger("eventResize",d[0],a,b,c,e,{})},select:function(a,b){this.unselect(b),this.renderSelection(a),this.reportSelection(a,b)},renderSelection:function(a){},reportSelection:function(a,b){this.isSelected=!0,this.triggerSelect(a,b)},triggerSelect:function(a,b){this.trigger("select",null,this.calendar.applyTimezone(a.start),this.calendar.applyTimezone(a.end),b)},unselect:function(a){this.isSelected&&(this.isSelected=!1,this.destroySelection&&this.destroySelection(),this.unrenderSelection(),this.trigger("unselect",null,a))},unrenderSelection:function(){},selectEvent:function(a){this.selectedEvent&&this.selectedEvent===a||(this.unselectEvent(),this.renderedEventSegEach(function(a){a.el.addClass("fc-selected")},a),this.selectedEvent=a)},unselectEvent:function(){this.selectedEvent&&(this.renderedEventSegEach(function(a){a.el.removeClass("fc-selected");
+},this.selectedEvent),this.selectedEvent=null)},isEventSelected:function(a){return this.selectedEvent&&this.selectedEvent._id===a._id},handleDocumentMousedown:function(a){u(a)&&this.processUnselect(a)},processUnselect:function(a){this.processRangeUnselect(a),this.processEventUnselect(a)},processRangeUnselect:function(b){var c;this.isSelected&&this.opt("unselectAuto")&&(c=this.opt("unselectCancel"),c&&a(b.target).closest(c).length||this.unselect(b))},processEventUnselect:function(b){this.selectedEvent&&(a(b.target).closest(".fc-selected").length||this.unselectEvent())},triggerDayClick:function(a,b,c){this.trigger("dayClick",b,this.calendar.applyTimezone(a.start),c)},initHiddenDays:function(){var b,c=this.opt("hiddenDays")||[],d=[],e=0;for(this.opt("weekends")===!1&&c.push(0,6),b=0;7>b;b++)(d[b]=-1!==a.inArray(b,c))||e++;if(!e)throw"invalid hiddenDays";this.isHiddenDayHash=d},isHiddenDay:function(a){return b.isMoment(a)&&(a=a.day()),this.isHiddenDayHash[a]},skipHiddenDays:function(a,b,c){var d=a.clone();for(b=b||1;this.isHiddenDayHash[(d.day()+(c?b:0)+7)%7];)d.add(b,"days");return d},computeDayRange:function(a){var b,c=a.start.clone().stripTime(),d=a.end,e=null;return d&&(e=d.clone().stripTime(),b=+d.time(),b&&b>=this.nextDayThreshold&&e.add(1,"days")),(!d||c>=e)&&(e=c.clone().add(1,"days")),{start:c,end:e}},isMultiDayEvent:function(a){var b=this.computeDayRange(a);return b.end.diff(b.start,"days")>1}}),xb=Va.Scroller=xa.extend({el:null,scrollEl:null,overflowX:null,overflowY:null,constructor:function(a){a=a||{},this.overflowX=a.overflowX||a.overflow||"auto",this.overflowY=a.overflowY||a.overflow||"auto"},render:function(){this.el=this.renderEl(),this.applyOverflow()},renderEl:function(){return this.scrollEl=a('<div class="fc-scroller"></div>')},clear:function(){this.setHeight("auto"),this.applyOverflow()},destroy:function(){this.el.remove()},applyOverflow:function(){this.scrollEl.css({"overflow-x":this.overflowX,"overflow-y":this.overflowY})},lockOverflow:function(a){var b=this.overflowX,c=this.overflowY;a=a||this.getScrollbarWidths(),"auto"===b&&(b=a.top||a.bottom||this.scrollEl[0].scrollWidth-1>this.scrollEl[0].clientWidth?"scroll":"hidden"),"auto"===c&&(c=a.left||a.right||this.scrollEl[0].scrollHeight-1>this.scrollEl[0].clientHeight?"scroll":"hidden"),this.scrollEl.css({"overflow-x":b,"overflow-y":c})},setHeight:function(a){this.scrollEl.height(a)},getScrollTop:function(){return this.scrollEl.scrollTop()},setScrollTop:function(a){this.scrollEl.scrollTop(a)},getClientWidth:function(){return this.scrollEl[0].clientWidth},getClientHeight:function(){return this.scrollEl[0].clientHeight},getScrollbarWidths:function(){return q(this.scrollEl)}}),yb=Va.Calendar=xa.extend({dirDefaults:null,langDefaults:null,overrides:null,options:null,viewSpecCache:null,view:null,header:null,loadingLevel:0,constructor:Pa,initialize:function(){},initOptions:function(a){var b,e,f,g;a=d(a),b=a.lang,e=zb[b],e||(b=yb.defaults.lang,e=zb[b]||{}),f=ba(a.isRTL,e.isRTL,yb.defaults.isRTL),g=f?yb.rtlDefaults:{},this.dirDefaults=g,this.langDefaults=e,this.overrides=a,this.options=c([yb.defaults,g,e,a]),Qa(this.options),this.viewSpecCache={}},getViewSpec:function(a){var b=this.viewSpecCache;return b[a]||(b[a]=this.buildViewSpec(a))},getUnitViewSpec:function(b){var c,d,e;if(-1!=a.inArray(b,$a))for(c=this.header.getViewsWithButtons(),a.each(Va.views,function(a){c.push(a)}),d=0;d<c.length;d++)if(e=this.getViewSpec(c[d]),e&&e.singleUnit==b)return e},buildViewSpec:function(a){for(var d,e,f,g,h=this.overrides.views||{},i=[],j=[],k=[],l=a;l;)d=Wa[l],e=h[l],l=null,"function"==typeof d&&(d={"class":d}),d&&(i.unshift(d),j.unshift(d.defaults||{}),f=f||d.duration,l=l||d.type),e&&(k.unshift(e),f=f||e.duration,l=l||e.type);return d=W(i),d.type=a,d["class"]?(f&&(f=b.duration(f),f.valueOf()&&(d.duration=f,g=O(f),1===f.as(g)&&(d.singleUnit=g,k.unshift(h[g]||{})))),d.defaults=c(j),d.overrides=c(k),this.buildViewSpecOptions(d),this.buildViewSpecButtonText(d,a),d):!1},buildViewSpecOptions:function(a){a.options=c([yb.defaults,a.defaults,this.dirDefaults,this.langDefaults,this.overrides,a.overrides]),Qa(a.options)},buildViewSpecButtonText:function(a,b){function c(c){var d=c.buttonText||{};return d[b]||(a.singleUnit?d[a.singleUnit]:null)}a.buttonTextOverride=c(this.overrides)||a.overrides.buttonText,a.buttonTextDefault=c(this.langDefaults)||c(this.dirDefaults)||a.defaults.buttonText||c(yb.defaults)||(a.duration?this.humanizeDuration(a.duration):null)||b},instantiateView:function(a){var b=this.getViewSpec(a);return new b["class"](this,a,b.options,b.duration)},isValidViewType:function(a){return Boolean(this.getViewSpec(a))},pushLoading:function(){this.loadingLevel++||this.trigger("loading",null,!0,this.view)},popLoading:function(){--this.loadingLevel||this.trigger("loading",null,!1,this.view)},buildSelectSpan:function(a,b){var c,d=this.moment(a).stripZone();return c=b?this.moment(b).stripZone():d.hasTime()?d.clone().add(this.defaultTimedEventDuration):d.clone().add(this.defaultAllDayEventDuration),{start:d,end:c}}});yb.mixin(kb),yb.defaults={titleRangeSeparator:" — ",monthYearFormat:"MMMM YYYY",defaultTimedEventDuration:"02:00:00",defaultAllDayEventDuration:{days:1},forceEventDuration:!1,nextDayThreshold:"09:00:00",defaultView:"month",aspectRatio:1.35,header:{left:"title",center:"",right:"today prev,next"},weekends:!0,weekNumbers:!1,weekNumberTitle:"W",weekNumberCalculation:"local",scrollTime:"06:00:00",lazyFetching:!0,startParam:"start",endParam:"end",timezoneParam:"timezone",timezone:!1,isRTL:!1,buttonText:{prev:"prev",next:"next",prevYear:"prev year",nextYear:"next year",year:"year",today:"today",month:"month",week:"week",day:"day"},buttonIcons:{prev:"left-single-arrow",next:"right-single-arrow",prevYear:"left-double-arrow",nextYear:"right-double-arrow"},theme:!1,themeButtonIcons:{prev:"circle-triangle-w",next:"circle-triangle-e",prevYear:"seek-prev",nextYear:"seek-next"},dragOpacity:.75,dragRevertDuration:500,dragScroll:!0,unselectAuto:!0,dropAccept:"*",eventOrder:"title",eventLimit:!1,eventLimitText:"more",eventLimitClick:"popover",dayPopoverFormat:"LL",handleWindowResize:!0,windowResizeDelay:200,longPressDelay:1e3},yb.englishDefaults={dayPopoverFormat:"dddd, MMMM D"},yb.rtlDefaults={header:{left:"next,prev today",center:"",right:"title"},buttonIcons:{prev:"right-single-arrow",next:"left-single-arrow",prevYear:"right-double-arrow",nextYear:"left-double-arrow"},themeButtonIcons:{prev:"circle-triangle-e",next:"circle-triangle-w",nextYear:"seek-prev",prevYear:"seek-next"}};var zb=Va.langs={};Va.datepickerLang=function(b,c,d){var e=zb[b]||(zb[b]={});e.isRTL=d.isRTL,e.weekNumberTitle=d.weekHeader,a.each(Ab,function(a,b){e[a]=b(d)}),a.datepicker&&(a.datepicker.regional[c]=a.datepicker.regional[b]=d,a.datepicker.regional.en=a.datepicker.regional[""],a.datepicker.setDefaults(d))},Va.lang=function(b,d){var e,f;e=zb[b]||(zb[b]={}),d&&(e=zb[b]=c([e,d])),f=Ra(b),a.each(Bb,function(a,b){null==e[a]&&(e[a]=b(f,e))}),yb.defaults.lang=b};var Ab={buttonText:function(a){return{prev:da(a.prevText),next:da(a.nextText),today:da(a.currentText)}},monthYearFormat:function(a){return a.showMonthAfterYear?"YYYY["+a.yearSuffix+"] MMMM":"MMMM YYYY["+a.yearSuffix+"]"}},Bb={dayOfMonthFormat:function(a,b){var c=a.longDateFormat("l");return c=c.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g,""),b.isRTL?c+=" ddd":c="ddd "+c,c},mediumTimeFormat:function(a){return a.longDateFormat("LT").replace(/\s*a$/i,"a")},smallTimeFormat:function(a){return a.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"a")},extraSmallTimeFormat:function(a){return a.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"t")},hourFormat:function(a){return a.longDateFormat("LT").replace(":mm","").replace(/(\Wmm)$/,"").replace(/\s*a$/i,"a")},noMeridiemTimeFormat:function(a){return a.longDateFormat("LT").replace(/\s*a$/i,"")}},Cb={smallDayDateFormat:function(a){return a.isRTL?"D dd":"dd D"},weekFormat:function(a){return a.isRTL?"w[ "+a.weekNumberTitle+"]":"["+a.weekNumberTitle+" ]w"},smallWeekFormat:function(a){return a.isRTL?"w["+a.weekNumberTitle+"]":"["+a.weekNumberTitle+"]w"}};Va.lang("en",yb.englishDefaults),Va.sourceNormalizers=[],Va.sourceFetchers=[];var Db={dataType:"json",cache:!1},Eb=1;yb.prototype.getPeerEvents=function(a,b){var c,d,e=this.getEventCache(),f=[];for(c=0;c<e.length;c++)d=e[c],b&&b._id===d._id||f.push(d);return f};var Fb=Va.BasicView=wb.extend({scroller:null,dayGridClass:ub,dayGrid:null,dayNumbersVisible:!1,weekNumbersVisible:!1,weekNumberWidth:null,headContainerEl:null,headRowEl:null,initialize:function(){this.dayGrid=this.instantiateDayGrid(),this.scroller=new xb({overflowX:"hidden",overflowY:"auto"})},instantiateDayGrid:function(){var a=this.dayGridClass.extend(Gb);return new a(this)},setRange:function(a){wb.prototype.setRange.call(this,a),this.dayGrid.breakOnWeeks=/year|month|week/.test(this.intervalUnit),this.dayGrid.setRange(a)},computeRange:function(a){var b=wb.prototype.computeRange.call(this,a);return/year|month/.test(b.intervalUnit)&&(b.start.startOf("week"),b.start=this.skipHiddenDays(b.start),b.end.weekday()&&(b.end.add(1,"week").startOf("week"),b.end=this.skipHiddenDays(b.end,-1,!0))),b},renderDates:function(){this.dayNumbersVisible=this.dayGrid.rowCnt>1,this.weekNumbersVisible=this.opt("weekNumbers"),this.dayGrid.numbersVisible=this.dayNumbersVisible||this.weekNumbersVisible,this.el.addClass("fc-basic-view").html(this.renderSkeletonHtml()),this.renderHead(),this.scroller.render();var b=this.scroller.el.addClass("fc-day-grid-container"),c=a('<div class="fc-day-grid" />').appendTo(b);this.el.find(".fc-body > tr > td").append(b),this.dayGrid.setElement(c),this.dayGrid.renderDates(this.hasRigidRows())},renderHead:function(){this.headContainerEl=this.el.find(".fc-head-container").html(this.dayGrid.renderHeadHtml()),this.headRowEl=this.headContainerEl.find(".fc-row")},unrenderDates:function(){this.dayGrid.unrenderDates(),this.dayGrid.removeElement(),this.scroller.destroy()},renderBusinessHours:function(){this.dayGrid.renderBusinessHours()},renderSkeletonHtml:function(){return'<table><thead class="fc-head"><tr><td class="fc-head-container '+this.widgetHeaderClass+'"></td></tr></thead><tbody class="fc-body"><tr><td class="'+this.widgetContentClass+'"></td></tr></tbody></table>'},weekNumberStyleAttr:function(){return null!==this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},hasRigidRows:function(){var a=this.opt("eventLimit");return a&&"number"!=typeof a},updateWidth:function(){this.weekNumbersVisible&&(this.weekNumberWidth=k(this.el.find(".fc-week-number")))},setHeight:function(a,b){var c,d,g=this.opt("eventLimit");this.scroller.clear(),f(this.headRowEl),this.dayGrid.removeSegPopover(),g&&"number"==typeof g&&this.dayGrid.limitRows(g),c=this.computeScrollerHeight(a),this.setGridHeight(c,b),g&&"number"!=typeof g&&this.dayGrid.limitRows(g),b||(this.scroller.setHeight(c),d=this.scroller.getScrollbarWidths(),(d.left||d.right)&&(e(this.headRowEl,d),c=this.computeScrollerHeight(a),this.scroller.setHeight(c)),this.scroller.lockOverflow(d))},computeScrollerHeight:function(a){return a-l(this.el,this.scroller.el)},setGridHeight:function(a,b){b?j(this.dayGrid.rowEls):i(this.dayGrid.rowEls,a,!0)},queryScroll:function(){return this.scroller.getScrollTop()},setScroll:function(a){this.scroller.setScrollTop(a)},prepareHits:function(){this.dayGrid.prepareHits()},releaseHits:function(){this.dayGrid.releaseHits()},queryHit:function(a,b){return this.dayGrid.queryHit(a,b)},getHitSpan:function(a){return this.dayGrid.getHitSpan(a)},getHitEl:function(a){return this.dayGrid.getHitEl(a)},renderEvents:function(a){this.dayGrid.renderEvents(a),this.updateHeight()},getEventSegs:function(){return this.dayGrid.getEventSegs()},unrenderEvents:function(){this.dayGrid.unrenderEvents()},renderDrag:function(a,b){return this.dayGrid.renderDrag(a,b)},unrenderDrag:function(){this.dayGrid.unrenderDrag()},renderSelection:function(a){this.dayGrid.renderSelection(a)},unrenderSelection:function(){this.dayGrid.unrenderSelection()}}),Gb={renderHeadIntroHtml:function(){var a=this.view;return a.weekNumbersVisible?'<th class="fc-week-number '+a.widgetHeaderClass+'" '+a.weekNumberStyleAttr()+"><span>"+ca(a.opt("weekNumberTitle"))+"</span></th>":""},renderNumberIntroHtml:function(a){var b=this.view;return b.weekNumbersVisible?'<td class="fc-week-number" '+b.weekNumberStyleAttr()+"><span>"+this.getCellDate(a,0).format("w")+"</span></td>":""},renderBgIntroHtml:function(){var a=this.view;return a.weekNumbersVisible?'<td class="fc-week-number '+a.widgetContentClass+'" '+a.weekNumberStyleAttr()+"></td>":""},renderIntroHtml:function(){var a=this.view;return a.weekNumbersVisible?'<td class="fc-week-number" '+a.weekNumberStyleAttr()+"></td>":""}},Hb=Va.MonthView=Fb.extend({computeRange:function(a){var b,c=Fb.prototype.computeRange.call(this,a);return this.isFixedWeeks()&&(b=Math.ceil(c.end.diff(c.start,"weeks",!0)),c.end.add(6-b,"weeks")),c},setGridHeight:function(a,b){b=b||"variable"===this.opt("weekMode"),b&&(a*=this.rowCnt/6),i(this.dayGrid.rowEls,a,!b)},isFixedWeeks:function(){var a=this.opt("weekMode");return a?"fixed"===a:this.opt("fixedWeekCount")}});Wa.basic={"class":Fb},Wa.basicDay={type:"basic",duration:{days:1}},Wa.basicWeek={type:"basic",duration:{weeks:1}},Wa.month={"class":Hb,duration:{months:1},defaults:{fixedWeekCount:!0}};var Ib=Va.AgendaView=wb.extend({scroller:null,timeGridClass:vb,timeGrid:null,dayGridClass:ub,dayGrid:null,axisWidth:null,headContainerEl:null,noScrollRowEls:null,bottomRuleEl:null,initialize:function(){this.timeGrid=this.instantiateTimeGrid(),this.opt("allDaySlot")&&(this.dayGrid=this.instantiateDayGrid()),this.scroller=new xb({overflowX:"hidden",overflowY:"auto"})},instantiateTimeGrid:function(){var a=this.timeGridClass.extend(Jb);return new a(this)},instantiateDayGrid:function(){var a=this.dayGridClass.extend(Kb);return new a(this)},setRange:function(a){wb.prototype.setRange.call(this,a),this.timeGrid.setRange(a),this.dayGrid&&this.dayGrid.setRange(a)},renderDates:function(){this.el.addClass("fc-agenda-view").html(this.renderSkeletonHtml()),this.renderHead(),this.scroller.render();var b=this.scroller.el.addClass("fc-time-grid-container"),c=a('<div class="fc-time-grid" />').appendTo(b);this.el.find(".fc-body > tr > td").append(b),this.timeGrid.setElement(c),this.timeGrid.renderDates(),this.bottomRuleEl=a('<hr class="fc-divider '+this.widgetHeaderClass+'"/>').appendTo(this.timeGrid.el),this.dayGrid&&(this.dayGrid.setElement(this.el.find(".fc-day-grid")),this.dayGrid.renderDates(),this.dayGrid.bottomCoordPadding=this.dayGrid.el.next("hr").outerHeight()),this.noScrollRowEls=this.el.find(".fc-row:not(.fc-scroller *)")},renderHead:function(){this.headContainerEl=this.el.find(".fc-head-container").html(this.timeGrid.renderHeadHtml())},unrenderDates:function(){this.timeGrid.unrenderDates(),this.timeGrid.removeElement(),this.dayGrid&&(this.dayGrid.unrenderDates(),this.dayGrid.removeElement()),this.scroller.destroy()},renderSkeletonHtml:function(){return'<table><thead class="fc-head"><tr><td class="fc-head-container '+this.widgetHeaderClass+'"></td></tr></thead><tbody class="fc-body"><tr><td class="'+this.widgetContentClass+'">'+(this.dayGrid?'<div class="fc-day-grid"/><hr class="fc-divider '+this.widgetHeaderClass+'"/>':"")+"</td></tr></tbody></table>"},axisStyleAttr:function(){return null!==this.axisWidth?'style="width:'+this.axisWidth+'px"':""},renderBusinessHours:function(){this.timeGrid.renderBusinessHours(),this.dayGrid&&this.dayGrid.renderBusinessHours()},unrenderBusinessHours:function(){this.timeGrid.unrenderBusinessHours(),this.dayGrid&&this.dayGrid.unrenderBusinessHours()},getNowIndicatorUnit:function(){return this.timeGrid.getNowIndicatorUnit()},renderNowIndicator:function(a){this.timeGrid.renderNowIndicator(a)},unrenderNowIndicator:function(){this.timeGrid.unrenderNowIndicator()},updateSize:function(a){this.timeGrid.updateSize(a),wb.prototype.updateSize.call(this,a)},updateWidth:function(){this.axisWidth=k(this.el.find(".fc-axis"))},setHeight:function(a,b){var c,d,g;this.bottomRuleEl.hide(),this.scroller.clear(),f(this.noScrollRowEls),this.dayGrid&&(this.dayGrid.removeSegPopover(),c=this.opt("eventLimit"),c&&"number"!=typeof c&&(c=Lb),c&&this.dayGrid.limitRows(c)),b||(d=this.computeScrollerHeight(a),this.scroller.setHeight(d),g=this.scroller.getScrollbarWidths(),(g.left||g.right)&&(e(this.noScrollRowEls,g),d=this.computeScrollerHeight(a),this.scroller.setHeight(d)),this.scroller.lockOverflow(g),this.timeGrid.getTotalSlatHeight()<d&&this.bottomRuleEl.show())},computeScrollerHeight:function(a){return a-l(this.el,this.scroller.el)},computeInitialScroll:function(){var a=b.duration(this.opt("scrollTime")),c=this.timeGrid.computeTimeTop(a);return c=Math.ceil(c),c&&c++,c},queryScroll:function(){return this.scroller.getScrollTop()},setScroll:function(a){this.scroller.setScrollTop(a)},prepareHits:function(){this.timeGrid.prepareHits(),this.dayGrid&&this.dayGrid.prepareHits()},releaseHits:function(){this.timeGrid.releaseHits(),this.dayGrid&&this.dayGrid.releaseHits()},queryHit:function(a,b){var c=this.timeGrid.queryHit(a,b);return!c&&this.dayGrid&&(c=this.dayGrid.queryHit(a,b)),c},getHitSpan:function(a){return a.component.getHitSpan(a)},getHitEl:function(a){return a.component.getHitEl(a)},renderEvents:function(a){var b,c,d=[],e=[],f=[];for(c=0;c<a.length;c++)a[c].allDay?d.push(a[c]):e.push(a[c]);b=this.timeGrid.renderEvents(e),this.dayGrid&&(f=this.dayGrid.renderEvents(d)),this.updateHeight()},getEventSegs:function(){return this.timeGrid.getEventSegs().concat(this.dayGrid?this.dayGrid.getEventSegs():[])},unrenderEvents:function(){this.timeGrid.unrenderEvents(),this.dayGrid&&this.dayGrid.unrenderEvents()},renderDrag:function(a,b){return a.start.hasTime()?this.timeGrid.renderDrag(a,b):this.dayGrid?this.dayGrid.renderDrag(a,b):void 0},unrenderDrag:function(){this.timeGrid.unrenderDrag(),this.dayGrid&&this.dayGrid.unrenderDrag()},renderSelection:function(a){a.start.hasTime()||a.end.hasTime()?this.timeGrid.renderSelection(a):this.dayGrid&&this.dayGrid.renderSelection(a)},unrenderSelection:function(){this.timeGrid.unrenderSelection(),this.dayGrid&&this.dayGrid.unrenderSelection()}}),Jb={renderHeadIntroHtml:function(){var a,b=this.view;return b.opt("weekNumbers")?(a=this.start.format(b.opt("smallWeekFormat")),'<th class="fc-axis fc-week-number '+b.widgetHeaderClass+'" '+b.axisStyleAttr()+"><span>"+ca(a)+"</span></th>"):'<th class="fc-axis '+b.widgetHeaderClass+'" '+b.axisStyleAttr()+"></th>"},renderBgIntroHtml:function(){var a=this.view;return'<td class="fc-axis '+a.widgetContentClass+'" '+a.axisStyleAttr()+"></td>"},renderIntroHtml:function(){var a=this.view;return'<td class="fc-axis" '+a.axisStyleAttr()+"></td>"}},Kb={renderBgIntroHtml:function(){var a=this.view;return'<td class="fc-axis '+a.widgetContentClass+'" '+a.axisStyleAttr()+"><span>"+(a.opt("allDayHtml")||ca(a.opt("allDayText")))+"</span></td>"},renderIntroHtml:function(){var a=this.view;return'<td class="fc-axis" '+a.axisStyleAttr()+"></td>"}},Lb=5,Mb=[{hours:1},{minutes:30},{minutes:15},{seconds:30},{seconds:15}];return Wa.agenda={"class":Ib,defaults:{allDaySlot:!0,allDayText:"all-day",slotDuration:"00:30:00",minTime:"00:00:00",maxTime:"24:00:00",slotEventOverlap:!0}},Wa.agendaDay={type:"agenda",duration:{days:1}},Wa.agendaWeek={type:"agenda",duration:{weeks:1}},Va}); \ No newline at end of file
diff --git a/tools/infra-dashboard/js/highslide-full.min.js b/tools/infra-dashboard/js/highslide-full.min.js
new file mode 100644
index 00000000..03c786f9
--- /dev/null
+++ b/tools/infra-dashboard/js/highslide-full.min.js
@@ -0,0 +1,3315 @@
+/******************************************************************************
+Name: Highslide JS
+Version: 4.1.8 (October 27 2009)
+Config: default +events +unobtrusive +imagemap +slideshow +positioning +transitions +viewport +thumbstrip +inline +ajax +iframe +flash
+Author: Torstein Hønsi
+Support: http://highslide.com/support
+
+Licence:
+Highslide JS is licensed under a Creative Commons Attribution-NonCommercial 2.5
+License (http://creativecommons.org/licenses/by-nc/2.5/).
+
+You are free:
+ * to copy, distribute, display, and perform the work
+ * to make derivative works
+
+Under the following conditions:
+ * Attribution. You must attribute the work in the manner specified by the
+ author or licensor.
+ * Noncommercial. You may not use this work for commercial purposes.
+
+* For any reuse or distribution, you must make clear to others the license
+ terms of this work.
+* Any of these conditions can be waived if you get permission from the
+ copyright holder.
+
+Your fair use and other rights are in no way affected by the above.
+******************************************************************************/
+if (!hs) { var hs = {
+// Language strings
+lang : {
+ cssDirection: 'ltr',
+ loadingText : 'Loading...',
+ loadingTitle : 'Click to cancel',
+ focusTitle : 'Click to bring to front',
+ fullExpandTitle : 'Expand to actual size (f)',
+ creditsText : 'Powered by <i>Highslide JS</i>',
+ creditsTitle : 'Go to the Highslide JS homepage',
+ previousText : 'Previous',
+ nextText : 'Next',
+ moveText : 'Move',
+ closeText : 'Close',
+ closeTitle : 'Close (esc)',
+ resizeTitle : 'Resize',
+ playText : 'Play',
+ playTitle : 'Play slideshow (spacebar)',
+ pauseText : 'Pause',
+ pauseTitle : 'Pause slideshow (spacebar)',
+ previousTitle : 'Previous (arrow left)',
+ nextTitle : 'Next (arrow right)',
+ moveTitle : 'Move',
+ fullExpandText : '1:1',
+ number: 'Image %1 of %2',
+ restoreTitle : 'Click to close image, click and drag to move. Use arrow keys for next and previous.'
+},
+// See http://highslide.com/ref for examples of settings
+graphicsDir : '../media/',
+expandCursor : 'zoomin.cur', // null disables
+restoreCursor : 'zoomout.cur', // null disables
+expandDuration : 250, // milliseconds
+restoreDuration : 250,
+marginLeft : 15,
+marginRight : 15,
+marginTop : 15,
+marginBottom : 15,
+zIndexCounter : 1001, // adjust to other absolutely positioned elements
+loadingOpacity : 0.75,
+allowMultipleInstances: true,
+numberOfImagesToPreload : 5,
+outlineWhileAnimating : 2, // 0 = never, 1 = always, 2 = HTML only
+outlineStartOffset : 3, // ends at 10
+padToMinWidth : false, // pad the popup width to make room for wide caption
+fullExpandPosition : 'bottom right',
+fullExpandOpacity : 1,
+showCredits : true, // you can set this to false if you want
+creditsHref : 'http://highslide.com/',
+creditsTarget : '_self',
+enableKeyListener : true,
+openerTagNames : ['a', 'area'], // Add more to allow slideshow indexing
+transitions : [],
+transitionDuration: 250,
+dimmingOpacity: 0, // Lightbox style dimming background
+dimmingDuration: 50, // 0 for instant dimming
+
+allowWidthReduction : false,
+allowHeightReduction : true,
+preserveContent : true, // Preserve changes made to the content and position of HTML popups.
+objectLoadTime : 'before', // Load iframes 'before' or 'after' expansion.
+cacheAjax : true, // Cache ajax popups for instant display. Can be overridden for each popup.
+anchor : 'auto', // where the image expands from
+align : 'auto', // position in the client (overrides anchor)
+targetX: null, // the id of a target element
+targetY: null,
+dragByHeading: true,
+minWidth: 200,
+minHeight: 200,
+allowSizeReduction: true, // allow the image to reduce to fit client size. If false, this overrides minWidth and minHeight
+outlineType : 'drop-shadow', // set null to disable outlines
+skin : {
+ controls:
+ '<div class="highslide-controls"><ul>'+
+ '<li class="highslide-previous">'+
+ '<a href="#" title="{hs.lang.previousTitle}">'+
+ '<span>{hs.lang.previousText}</span></a>'+
+ '</li>'+
+ '<li class="highslide-play">'+
+ '<a href="#" title="{hs.lang.playTitle}">'+
+ '<span>{hs.lang.playText}</span></a>'+
+ '</li>'+
+ '<li class="highslide-pause">'+
+ '<a href="#" title="{hs.lang.pauseTitle}">'+
+ '<span>{hs.lang.pauseText}</span></a>'+
+ '</li>'+
+ '<li class="highslide-next">'+
+ '<a href="#" title="{hs.lang.nextTitle}">'+
+ '<span>{hs.lang.nextText}</span></a>'+
+ '</li>'+
+ '<li class="highslide-move">'+
+ '<a href="#" title="{hs.lang.moveTitle}">'+
+ '<span>{hs.lang.moveText}</span></a>'+
+ '</li>'+
+ '<li class="highslide-full-expand">'+
+ '<a href="#" title="{hs.lang.fullExpandTitle}">'+
+ '<span>{hs.lang.fullExpandText}</span></a>'+
+ '</li>'+
+ '<li class="highslide-close">'+
+ '<a href="#" title="{hs.lang.closeTitle}" >'+
+ '<span>{hs.lang.closeText}</span></a>'+
+ '</li>'+
+ '</ul></div>'
+ ,
+ contentWrapper:
+ '<div class="highslide-header"><ul>'+
+ '<li class="highslide-previous">'+
+ '<a href="#" title="{hs.lang.previousTitle}" onclick="return hs.previous(this)">'+
+ '<span>{hs.lang.previousText}</span></a>'+
+ '</li>'+
+ '<li class="highslide-next">'+
+ '<a href="#" title="{hs.lang.nextTitle}" onclick="return hs.next(this)">'+
+ '<span>{hs.lang.nextText}</span></a>'+
+ '</li>'+
+ '<li class="highslide-move">'+
+ '<a href="#" title="{hs.lang.moveTitle}" onclick="return false">'+
+ '<span>{hs.lang.moveText}</span></a>'+
+ '</li>'+
+ '<li class="highslide-close">'+
+ '<a href="#" title="{hs.lang.closeTitle}" onclick="return hs.close(this)">'+
+ '<span>{hs.lang.closeText}</span></a>'+
+ '</li>'+
+ '</ul></div>'+
+ '<div class="highslide-body"></div>'+
+ '<div class="highslide-footer"><div>'+
+ '<span class="highslide-resize" title="{hs.lang.resizeTitle}"><span></span></span>'+
+ '</div></div>'
+},
+// END OF YOUR SETTINGS
+
+
+// declare internal properties
+preloadTheseImages : [],
+continuePreloading: true,
+expanders : [],
+overrides : [
+ 'allowSizeReduction',
+ 'useBox',
+ 'anchor',
+ 'align',
+ 'targetX',
+ 'targetY',
+ 'outlineType',
+ 'outlineWhileAnimating',
+ 'captionId',
+ 'captionText',
+ 'captionEval',
+ 'captionOverlay',
+ 'headingId',
+ 'headingText',
+ 'headingEval',
+ 'headingOverlay',
+ 'creditsPosition',
+ 'dragByHeading',
+ 'autoplay',
+ 'numberPosition',
+ 'transitions',
+ 'dimmingOpacity',
+
+ 'width',
+ 'height',
+
+ 'contentId',
+ 'allowWidthReduction',
+ 'allowHeightReduction',
+ 'preserveContent',
+ 'maincontentId',
+ 'maincontentText',
+ 'maincontentEval',
+ 'objectType',
+ 'cacheAjax',
+ 'objectWidth',
+ 'objectHeight',
+ 'objectLoadTime',
+ 'swfOptions',
+ 'wrapperClassName',
+ 'minWidth',
+ 'minHeight',
+ 'maxWidth',
+ 'maxHeight',
+ 'pageOrigin',
+ 'slideshowGroup',
+ 'easing',
+ 'easingClose',
+ 'fadeInOut',
+ 'src'
+],
+overlays : [],
+idCounter : 0,
+oPos : {
+ x: ['leftpanel', 'left', 'center', 'right', 'rightpanel'],
+ y: ['above', 'top', 'middle', 'bottom', 'below']
+},
+mouse: {},
+headingOverlay: {},
+captionOverlay: {},
+swfOptions: { flashvars: {}, params: {}, attributes: {} },
+timers : [],
+
+slideshows : [],
+
+pendingOutlines : {},
+sleeping : [],
+preloadTheseAjax : [],
+cacheBindings : [],
+cachedGets : {},
+clones : {},
+onReady: [],
+uaVersion: /Trident\/4\.0/.test(navigator.userAgent) ? 8 :
+ parseFloat((navigator.userAgent.toLowerCase().match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1]),
+ie : (document.all && !window.opera),
+safari : /Safari/.test(navigator.userAgent),
+geckoMac : /Macintosh.+rv:1\.[0-8].+Gecko/.test(navigator.userAgent),
+
+$ : function (id) {
+ if (id) return document.getElementById(id);
+},
+
+push : function (arr, val) {
+ arr[arr.length] = val;
+},
+
+createElement : function (tag, attribs, styles, parent, nopad) {
+ var el = document.createElement(tag);
+ if (attribs) hs.extend(el, attribs);
+ if (nopad) hs.setStyles(el, {padding: 0, border: 'none', margin: 0});
+ if (styles) hs.setStyles(el, styles);
+ if (parent) parent.appendChild(el);
+ return el;
+},
+
+extend : function (el, attribs) {
+ for (var x in attribs) el[x] = attribs[x];
+ return el;
+},
+
+setStyles : function (el, styles) {
+ for (var x in styles) {
+ if (hs.ie && x == 'opacity') {
+ if (styles[x] > 0.99) el.style.removeAttribute('filter');
+ else el.style.filter = 'alpha(opacity='+ (styles[x] * 100) +')';
+ }
+ else el.style[x] = styles[x];
+ }
+},
+animate: function(el, prop, opt) {
+ var start,
+ end,
+ unit;
+ if (typeof opt != 'object' || opt === null) {
+ var args = arguments;
+ opt = {
+ duration: args[2],
+ easing: args[3],
+ complete: args[4]
+ };
+ }
+ if (typeof opt.duration != 'number') opt.duration = 250;
+ opt.easing = Math[opt.easing] || Math.easeInQuad;
+ opt.curAnim = hs.extend({}, prop);
+ for (var name in prop) {
+ var e = new hs.fx(el, opt , name );
+
+ start = parseFloat(hs.css(el, name)) || 0;
+ end = parseFloat(prop[name]);
+ unit = name != 'opacity' ? 'px' : '';
+
+ e.custom( start, end, unit );
+ }
+},
+css: function(el, prop) {
+ if (el.style[prop]) {
+ return el.style[prop];
+ } else if (document.defaultView) {
+ return document.defaultView.getComputedStyle(el, null).getPropertyValue(prop);
+
+ } else {
+ if (prop == 'opacity') prop = 'filter';
+ var val = el.currentStyle[prop.replace(/\-(\w)/g, function (a, b){ return b.toUpperCase(); })];
+ if (prop == 'filter')
+ val = val.replace(/alpha\(opacity=([0-9]+)\)/,
+ function (a, b) { return b / 100 });
+ return val === '' ? 1 : val;
+ }
+},
+
+getPageSize : function () {
+ var d = document, w = window, iebody = d.compatMode && d.compatMode != 'BackCompat'
+ ? d.documentElement : d.body;
+
+ var width = hs.ie ? iebody.clientWidth :
+ (d.documentElement.clientWidth || self.innerWidth),
+ height = hs.ie ? iebody.clientHeight : self.innerHeight;
+
+ hs.page = {
+ width: width,
+ height: height,
+ scrollLeft: hs.ie ? iebody.scrollLeft : pageXOffset,
+ scrollTop: hs.ie ? iebody.scrollTop : pageYOffset
+ };
+ return hs.page;
+},
+
+getPosition : function(el) {
+ if (/area/i.test(el.tagName)) {
+ var imgs = document.getElementsByTagName('img');
+ for (var i = 0; i < imgs.length; i++) {
+ var u = imgs[i].useMap;
+ if (u && u.replace(/^.*?#/, '') == el.parentNode.name) {
+ el = imgs[i];
+ break;
+ }
+ }
+ }
+ var p = { x: el.offsetLeft, y: el.offsetTop };
+ while (el.offsetParent) {
+ el = el.offsetParent;
+ p.x += el.offsetLeft;
+ p.y += el.offsetTop;
+ if (el != document.body && el != document.documentElement) {
+ p.x -= el.scrollLeft;
+ p.y -= el.scrollTop;
+ }
+ }
+ return p;
+},
+
+expand : function(a, params, custom, type) {
+ if (!a) a = hs.createElement('a', null, { display: 'none' }, hs.container);
+ if (typeof a.getParams == 'function') return params;
+ if (type == 'html') {
+ for (var i = 0; i < hs.sleeping.length; i++) {
+ if (hs.sleeping[i] && hs.sleeping[i].a == a) {
+ hs.sleeping[i].awake();
+ hs.sleeping[i] = null;
+ return false;
+ }
+ }
+ hs.hasHtmlExpanders = true;
+ }
+ try {
+ new hs.Expander(a, params, custom, type);
+ return false;
+ } catch (e) { return true; }
+},
+
+htmlExpand : function(a, params, custom) {
+ return hs.expand(a, params, custom, 'html');
+},
+
+getSelfRendered : function() {
+ return hs.createElement('div', {
+ className: 'highslide-html-content',
+ innerHTML: hs.replaceLang(hs.skin.contentWrapper)
+ });
+},
+getElementByClass : function (el, tagName, className) {
+ var els = el.getElementsByTagName(tagName);
+ for (var i = 0; i < els.length; i++) {
+ if ((new RegExp(className)).test(els[i].className)) {
+ return els[i];
+ }
+ }
+ return null;
+},
+replaceLang : function(s) {
+ s = s.replace(/\s/g, ' ');
+ var re = /{hs\.lang\.([^}]+)\}/g,
+ matches = s.match(re),
+ lang;
+ if (matches) for (var i = 0; i < matches.length; i++) {
+ lang = matches[i].replace(re, "$1");
+ if (typeof hs.lang[lang] != 'undefined') s = s.replace(matches[i], hs.lang[lang]);
+ }
+ return s;
+},
+
+
+setClickEvents : function () {
+ var els = document.getElementsByTagName('a');
+ for (var i = 0; i < els.length; i++) {
+ var type = hs.isUnobtrusiveAnchor(els[i]);
+ if (type && !els[i].hsHasSetClick) {
+ (function(){
+ var t = type;
+ if (hs.fireEvent(hs, 'onSetClickEvent', { element: els[i], type: t })) {
+ els[i].onclick =(type == 'image') ?function() { return hs.expand(this) }:
+ function() { return hs.htmlExpand(this, { objectType: t } );};
+ }
+ })();
+ els[i].hsHasSetClick = true;
+ }
+ }
+ hs.getAnchors();
+},
+isUnobtrusiveAnchor: function(el) {
+ if (el.rel == 'highslide') return 'image';
+ else if (el.rel == 'highslide-ajax') return 'ajax';
+ else if (el.rel == 'highslide-iframe') return 'iframe';
+ else if (el.rel == 'highslide-swf') return 'swf';
+},
+
+getCacheBinding : function (a) {
+ for (var i = 0; i < hs.cacheBindings.length; i++) {
+ if (hs.cacheBindings[i][0] == a) {
+ var c = hs.cacheBindings[i][1];
+ hs.cacheBindings[i][1] = c.cloneNode(1);
+ return c;
+ }
+ }
+ return null;
+},
+
+preloadAjax : function (e) {
+ var arr = hs.getAnchors();
+ for (var i = 0; i < arr.htmls.length; i++) {
+ var a = arr.htmls[i];
+ if (hs.getParam(a, 'objectType') == 'ajax' && hs.getParam(a, 'cacheAjax'))
+ hs.push(hs.preloadTheseAjax, a);
+ }
+
+ hs.preloadAjaxElement(0);
+},
+
+preloadAjaxElement : function (i) {
+ if (!hs.preloadTheseAjax[i]) return;
+ var a = hs.preloadTheseAjax[i];
+ var cache = hs.getNode(hs.getParam(a, 'contentId'));
+ if (!cache) cache = hs.getSelfRendered();
+ var ajax = new hs.Ajax(a, cache, 1);
+ ajax.onError = function () { };
+ ajax.onLoad = function () {
+ hs.push(hs.cacheBindings, [a, cache]);
+ hs.preloadAjaxElement(i + 1);
+ };
+ ajax.run();
+},
+
+focusTopmost : function() {
+ var topZ = 0,
+ topmostKey = -1,
+ expanders = hs.expanders,
+ exp,
+ zIndex;
+ for (var i = 0; i < expanders.length; i++) {
+ exp = expanders[i];
+ if (exp) {
+ zIndex = exp.wrapper.style.zIndex;
+ if (zIndex && zIndex > topZ) {
+ topZ = zIndex;
+ topmostKey = i;
+ }
+ }
+ }
+ if (topmostKey == -1) hs.focusKey = -1;
+ else expanders[topmostKey].focus();
+},
+
+getParam : function (a, param) {
+ a.getParams = a.onclick;
+ var p = a.getParams ? a.getParams() : null;
+ a.getParams = null;
+
+ return (p && typeof p[param] != 'undefined') ? p[param] :
+ (typeof hs[param] != 'undefined' ? hs[param] : null);
+},
+
+getSrc : function (a) {
+ var src = hs.getParam(a, 'src');
+ if (src) return src;
+ return a.href;
+},
+
+getNode : function (id) {
+ var node = hs.$(id), clone = hs.clones[id], a = {};
+ if (!node && !clone) return null;
+ if (!clone) {
+ clone = node.cloneNode(true);
+ clone.id = '';
+ hs.clones[id] = clone;
+ return node;
+ } else {
+ return clone.cloneNode(true);
+ }
+},
+
+discardElement : function(d) {
+ if (d) hs.garbageBin.appendChild(d);
+ hs.garbageBin.innerHTML = '';
+},
+dim : function(exp) {
+ if (!hs.dimmer) {
+ hs.dimmer = hs.createElement ('div', {
+ className: 'highslide-dimming highslide-viewport-size',
+ owner: '',
+ onclick: function() {
+ if (hs.fireEvent(hs, 'onDimmerClick'))
+
+ hs.close();
+ }
+ }, {
+ visibility: 'visible',
+ opacity: 0
+ }, hs.container, true);
+ }
+
+ hs.dimmer.style.display = '';
+
+ hs.dimmer.owner += '|'+ exp.key;
+ if (hs.geckoMac && hs.dimmingGeckoFix)
+ hs.setStyles(hs.dimmer, {
+ background: 'url('+ hs.graphicsDir + 'geckodimmer.png)',
+ opacity: 1
+ });
+ else
+ hs.animate(hs.dimmer, { opacity: exp.dimmingOpacity }, hs.dimmingDuration);
+},
+undim : function(key) {
+ if (!hs.dimmer) return;
+ if (typeof key != 'undefined') hs.dimmer.owner = hs.dimmer.owner.replace('|'+ key, '');
+
+ if (
+ (typeof key != 'undefined' && hs.dimmer.owner != '')
+ || (hs.upcoming && hs.getParam(hs.upcoming, 'dimmingOpacity'))
+ ) return;
+
+ if (hs.geckoMac && hs.dimmingGeckoFix) hs.dimmer.style.display = 'none';
+ else hs.animate(hs.dimmer, { opacity: 0 }, hs.dimmingDuration, null, function() {
+ hs.dimmer.style.display = 'none';
+ });
+},
+transit : function (adj, exp) {
+ var last = exp || hs.getExpander();
+ exp = last;
+ if (hs.upcoming) return false;
+ else hs.last = last;
+ hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
+ try {
+ hs.upcoming = adj;
+ adj.onclick();
+ } catch (e){
+ hs.last = hs.upcoming = null;
+ }
+ try {
+ if (!adj || exp.transitions[1] != 'crossfade')
+ exp.close();
+ } catch (e) {}
+ return false;
+},
+
+previousOrNext : function (el, op) {
+ var exp = hs.getExpander(el);
+ if (exp) return hs.transit(exp.getAdjacentAnchor(op), exp);
+ else return false;
+},
+
+previous : function (el) {
+ return hs.previousOrNext(el, -1);
+},
+
+next : function (el) {
+ return hs.previousOrNext(el, 1);
+},
+
+keyHandler : function(e) {
+ if (!e) e = window.event;
+ if (!e.target) e.target = e.srcElement; // ie
+ if (typeof e.target.form != 'undefined') return true; // form element has focus
+ if (!hs.fireEvent(hs, 'onKeyDown', e)) return true;
+ var exp = hs.getExpander();
+
+ var op = null;
+ switch (e.keyCode) {
+ case 70: // f
+ if (exp) exp.doFullExpand();
+ return true;
+ case 32: // Space
+ op = 2;
+ break;
+ case 34: // Page Down
+ case 39: // Arrow right
+ case 40: // Arrow down
+ op = 1;
+ break;
+ case 8: // Backspace
+ case 33: // Page Up
+ case 37: // Arrow left
+ case 38: // Arrow up
+ op = -1;
+ break;
+ case 27: // Escape
+ case 13: // Enter
+ op = 0;
+ }
+ if (op !== null) {
+ hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
+ if (!hs.enableKeyListener) return true;
+
+ if (e.preventDefault) e.preventDefault();
+ else e.returnValue = false;
+ if (exp) {
+ if (op == 0) {
+ exp.close();
+ } else if (op == 2) {
+ if (exp.slideshow) exp.slideshow.hitSpace();
+ } else {
+ if (exp.slideshow) exp.slideshow.pause();
+ hs.previousOrNext(exp.key, op);
+ }
+ return false;
+ }
+ }
+ return true;
+},
+
+
+registerOverlay : function (overlay) {
+ hs.push(hs.overlays, hs.extend(overlay, { hsId: 'hsId'+ hs.idCounter++ } ));
+},
+
+
+addSlideshow : function (options) {
+ var sg = options.slideshowGroup;
+ if (typeof sg == 'object') {
+ for (var i = 0; i < sg.length; i++) {
+ var o = {};
+ for (var x in options) o[x] = options[x];
+ o.slideshowGroup = sg[i];
+ hs.push(hs.slideshows, o);
+ }
+ } else {
+ hs.push(hs.slideshows, options);
+ }
+},
+
+getWrapperKey : function (element, expOnly) {
+ var el, re = /^highslide-wrapper-([0-9]+)$/;
+ // 1. look in open expanders
+ el = element;
+ while (el.parentNode) {
+ if (el.hsKey !== undefined) return el.hsKey;
+ if (el.id && re.test(el.id)) return el.id.replace(re, "$1");
+ el = el.parentNode;
+ }
+ // 2. look in thumbnail
+ if (!expOnly) {
+ el = element;
+ while (el.parentNode) {
+ if (el.tagName && hs.isHsAnchor(el)) {
+ for (var key = 0; key < hs.expanders.length; key++) {
+ var exp = hs.expanders[key];
+ if (exp && exp.a == el) return key;
+ }
+ }
+ el = el.parentNode;
+ }
+ }
+ return null;
+},
+
+getExpander : function (el, expOnly) {
+ if (typeof el == 'undefined') return hs.expanders[hs.focusKey] || null;
+ if (typeof el == 'number') return hs.expanders[el] || null;
+ if (typeof el == 'string') el = hs.$(el);
+ return hs.expanders[hs.getWrapperKey(el, expOnly)] || null;
+},
+
+isHsAnchor : function (a) {
+ return (a.onclick && a.onclick.toString().replace(/\s/g, ' ').match(/hs.(htmlE|e)xpand/));
+},
+
+reOrder : function () {
+ for (var i = 0; i < hs.expanders.length; i++)
+ if (hs.expanders[i] && hs.expanders[i].isExpanded) hs.focusTopmost();
+},
+fireEvent : function (obj, evt, args) {
+ return obj && obj[evt] ? (obj[evt](obj, args) !== false) : true;
+},
+
+mouseClickHandler : function(e)
+{
+ if (!e) e = window.event;
+ if (e.button > 1) return true;
+ if (!e.target) e.target = e.srcElement;
+
+ var el = e.target;
+ while (el.parentNode
+ && !(/highslide-(image|move|html|resize)/.test(el.className)))
+ {
+ el = el.parentNode;
+ }
+ var exp = hs.getExpander(el);
+ if (exp && (exp.isClosing || !exp.isExpanded)) return true;
+
+ if (exp && e.type == 'mousedown') {
+ if (e.target.form) return true;
+ var match = el.className.match(/highslide-(image|move|resize)/);
+ if (match) {
+ hs.dragArgs = {
+ exp: exp ,
+ type: match[1],
+ left: exp.x.pos,
+ width: exp.x.size,
+ top: exp.y.pos,
+ height: exp.y.size,
+ clickX: e.clientX,
+ clickY: e.clientY
+ };
+
+
+ hs.addEventListener(document, 'mousemove', hs.dragHandler);
+ if (e.preventDefault) e.preventDefault(); // FF
+
+ if (/highslide-(image|html)-blur/.test(exp.content.className)) {
+ exp.focus();
+ hs.hasFocused = true;
+ }
+ return false;
+ }
+ else if (/highslide-html/.test(el.className) && hs.focusKey != exp.key) {
+ exp.focus();
+ exp.doShowHide('hidden');
+ }
+ } else if (e.type == 'mouseup') {
+
+ hs.removeEventListener(document, 'mousemove', hs.dragHandler);
+
+ if (hs.dragArgs) {
+ if (hs.styleRestoreCursor && hs.dragArgs.type == 'image')
+ hs.dragArgs.exp.content.style.cursor = hs.styleRestoreCursor;
+ var hasDragged = hs.dragArgs.hasDragged;
+
+ if (!hasDragged &&!hs.hasFocused && !/(move|resize)/.test(hs.dragArgs.type)) {
+ if (hs.fireEvent(exp, 'onImageClick'))
+ exp.close();
+ }
+ else if (hasDragged || (!hasDragged && hs.hasHtmlExpanders)) {
+ hs.dragArgs.exp.doShowHide('hidden');
+ }
+
+ if (hs.dragArgs.exp.releaseMask)
+ hs.dragArgs.exp.releaseMask.style.display = 'none';
+
+ if (hasDragged) hs.fireEvent(hs.dragArgs.exp, 'onDrop', hs.dragArgs);
+ hs.hasFocused = false;
+ hs.dragArgs = null;
+
+ } else if (/highslide-image-blur/.test(el.className)) {
+ el.style.cursor = hs.styleRestoreCursor;
+ }
+ }
+ return false;
+},
+
+dragHandler : function(e)
+{
+ if (!hs.dragArgs) return true;
+ if (!e) e = window.event;
+ var a = hs.dragArgs, exp = a.exp;
+ if (exp.iframe) {
+ if (!exp.releaseMask) exp.releaseMask = hs.createElement('div', null,
+ { position: 'absolute', width: exp.x.size+'px', height: exp.y.size+'px',
+ left: exp.x.cb+'px', top: exp.y.cb+'px', zIndex: 4, background: (hs.ie ? 'white' : 'none'),
+ opacity: 0.01 },
+ exp.wrapper, true);
+ if (exp.releaseMask.style.display == 'none')
+ exp.releaseMask.style.display = '';
+ }
+
+ a.dX = e.clientX - a.clickX;
+ a.dY = e.clientY - a.clickY;
+
+ var distance = Math.sqrt(Math.pow(a.dX, 2) + Math.pow(a.dY, 2));
+ if (!a.hasDragged) a.hasDragged = (a.type != 'image' && distance > 0)
+ || (distance > (hs.dragSensitivity || 5));
+
+ if (a.hasDragged && e.clientX > 5 && e.clientY > 5) {
+ if (!hs.fireEvent(exp, 'onDrag', a)) return false;
+
+ if (a.type == 'resize') exp.resize(a);
+ else {
+ exp.moveTo(a.left + a.dX, a.top + a.dY);
+ if (a.type == 'image') exp.content.style.cursor = 'move';
+ }
+ }
+ return false;
+},
+
+wrapperMouseHandler : function (e) {
+ try {
+ if (!e) e = window.event;
+ var over = /mouseover/i.test(e.type);
+ if (!e.target) e.target = e.srcElement; // ie
+ if (hs.ie) e.relatedTarget =
+ over ? e.fromElement : e.toElement; // ie
+ var exp = hs.getExpander(e.target);
+ if (!exp.isExpanded) return;
+ if (!exp || !e.relatedTarget || hs.getExpander(e.relatedTarget, true) == exp
+ || hs.dragArgs) return;
+ hs.fireEvent(exp, over ? 'onMouseOver' : 'onMouseOut', e);
+ for (var i = 0; i < exp.overlays.length; i++) (function() {
+ var o = hs.$('hsId'+ exp.overlays[i]);
+ if (o && o.hideOnMouseOut) {
+ if (over) hs.setStyles(o, { visibility: 'visible', display: '' });
+ hs.animate(o, { opacity: over ? o.opacity : 0 }, o.dur);
+ }
+ })();
+ } catch (e) {}
+},
+addEventListener : function (el, event, func) {
+ if (el == document && event == 'ready') {
+ hs.push(hs.onReady, func);
+ }
+ try {
+ el.addEventListener(event, func, false);
+ } catch (e) {
+ try {
+ el.detachEvent('on'+ event, func);
+ el.attachEvent('on'+ event, func);
+ } catch (e) {
+ el['on'+ event] = func;
+ }
+ }
+},
+
+removeEventListener : function (el, event, func) {
+ try {
+ el.removeEventListener(event, func, false);
+ } catch (e) {
+ try {
+ el.detachEvent('on'+ event, func);
+ } catch (e) {
+ el['on'+ event] = null;
+ }
+ }
+},
+
+preloadFullImage : function (i) {
+ if (hs.continuePreloading && hs.preloadTheseImages[i] && hs.preloadTheseImages[i] != 'undefined') {
+ var img = document.createElement('img');
+ img.onload = function() {
+ img = null;
+ hs.preloadFullImage(i + 1);
+ };
+ img.src = hs.preloadTheseImages[i];
+ }
+},
+preloadImages : function (number) {
+ if (number && typeof number != 'object') hs.numberOfImagesToPreload = number;
+
+ var arr = hs.getAnchors();
+ for (var i = 0; i < arr.images.length && i < hs.numberOfImagesToPreload; i++) {
+ hs.push(hs.preloadTheseImages, hs.getSrc(arr.images[i]));
+ }
+
+ // preload outlines
+ if (hs.outlineType) new hs.Outline(hs.outlineType, function () { hs.preloadFullImage(0)} );
+ else
+
+ hs.preloadFullImage(0);
+
+ // preload cursor
+ if (hs.restoreCursor) var cur = hs.createElement('img', { src: hs.graphicsDir + hs.restoreCursor });
+},
+
+
+init : function () {
+ if (!hs.container) {
+
+ hs.getPageSize();
+ hs.ieLt7 = hs.ie && hs.uaVersion < 7;
+ hs.ie6SSL = hs.ieLt7 && location.protocol == 'https:';
+ for (var x in hs.langDefaults) {
+ if (typeof hs[x] != 'undefined') hs.lang[x] = hs[x];
+ else if (typeof hs.lang[x] == 'undefined' && typeof hs.langDefaults[x] != 'undefined')
+ hs.lang[x] = hs.langDefaults[x];
+ }
+
+ hs.container = hs.createElement('div', {
+ className: 'highslide-container'
+ }, {
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: '100%',
+ zIndex: hs.zIndexCounter,
+ direction: 'ltr'
+ },
+ document.body,
+ true
+ );
+ hs.loading = hs.createElement('a', {
+ className: 'highslide-loading',
+ title: hs.lang.loadingTitle,
+ innerHTML: hs.lang.loadingText,
+ href: 'javascript:;'
+ }, {
+ position: 'absolute',
+ top: '-9999px',
+ opacity: hs.loadingOpacity,
+ zIndex: 1
+ }, hs.container
+ );
+ hs.garbageBin = hs.createElement('div', null, { display: 'none' }, hs.container);
+ hs.viewport = hs.createElement('div', {
+ className: 'highslide-viewport highslide-viewport-size'
+ }, {
+ visibility: (hs.safari && hs.uaVersion < 525) ? 'visible' : 'hidden'
+ }, hs.container, 1
+ );
+ hs.clearing = hs.createElement('div', null,
+ { clear: 'both', paddingTop: '1px' }, null, true);
+
+ // http://www.robertpenner.com/easing/
+ Math.linearTween = function (t, b, c, d) {
+ return c*t/d + b;
+ };
+ Math.easeInQuad = function (t, b, c, d) {
+ return c*(t/=d)*t + b;
+ };
+ Math.easeOutQuad = function (t, b, c, d) {
+ return -c *(t/=d)*(t-2) + b;
+ };
+
+ hs.hideSelects = hs.ieLt7;
+ hs.hideIframes = ((window.opera && hs.uaVersion < 9) || navigator.vendor == 'KDE'
+ || (hs.ie && hs.uaVersion < 5.5));
+ hs.fireEvent(this, 'onActivate');
+ }
+},
+ready : function() {
+ if (hs.isReady) return;
+ hs.isReady = true;
+ for (var i = 0; i < hs.onReady.length; i++) hs.onReady[i]();
+},
+
+updateAnchors : function() {
+ var el, els, all = [], images = [], htmls = [],groups = {}, re;
+
+ for (var i = 0; i < hs.openerTagNames.length; i++) {
+ els = document.getElementsByTagName(hs.openerTagNames[i]);
+ for (var j = 0; j < els.length; j++) {
+ el = els[j];
+ re = hs.isHsAnchor(el);
+ if (re) {
+ hs.push(all, el);
+ if (re[0] == 'hs.expand') hs.push(images, el);
+ else if (re[0] == 'hs.htmlExpand') hs.push(htmls, el);
+ var g = hs.getParam(el, 'slideshowGroup') || 'none';
+ if (!groups[g]) groups[g] = [];
+ hs.push(groups[g], el);
+ }
+ }
+ }
+ hs.anchors = { all: all, groups: groups, images: images, htmls: htmls };
+ return hs.anchors;
+
+},
+
+getAnchors : function() {
+ return hs.anchors || hs.updateAnchors();
+},
+
+
+close : function(el) {
+ var exp = hs.getExpander(el);
+ if (exp) exp.close();
+ return false;
+}
+}; // end hs object
+hs.fx = function( elem, options, prop ){
+ this.options = options;
+ this.elem = elem;
+ this.prop = prop;
+
+ if (!options.orig) options.orig = {};
+};
+hs.fx.prototype = {
+ update: function(){
+ (hs.fx.step[this.prop] || hs.fx.step._default)(this);
+
+ if (this.options.step)
+ this.options.step.call(this.elem, this.now, this);
+
+ },
+ custom: function(from, to, unit){
+ this.startTime = (new Date()).getTime();
+ this.start = from;
+ this.end = to;
+ this.unit = unit;// || this.unit || "px";
+ this.now = this.start;
+ this.pos = this.state = 0;
+
+ var self = this;
+ function t(gotoEnd){
+ return self.step(gotoEnd);
+ }
+
+ t.elem = this.elem;
+
+ if ( t() && hs.timers.push(t) == 1 ) {
+ hs.timerId = setInterval(function(){
+ var timers = hs.timers;
+
+ for ( var i = 0; i < timers.length; i++ )
+ if ( !timers[i]() )
+ timers.splice(i--, 1);
+
+ if ( !timers.length ) {
+ clearInterval(hs.timerId);
+ }
+ }, 13);
+ }
+ },
+ step: function(gotoEnd){
+ var t = (new Date()).getTime();
+ if ( gotoEnd || t >= this.options.duration + this.startTime ) {
+ this.now = this.end;
+ this.pos = this.state = 1;
+ this.update();
+
+ this.options.curAnim[ this.prop ] = true;
+
+ var done = true;
+ for ( var i in this.options.curAnim )
+ if ( this.options.curAnim[i] !== true )
+ done = false;
+
+ if ( done ) {
+ if (this.options.complete) this.options.complete.call(this.elem);
+ }
+ return false;
+ } else {
+ var n = t - this.startTime;
+ this.state = n / this.options.duration;
+ this.pos = this.options.easing(n, 0, 1, this.options.duration);
+ this.now = this.start + ((this.end - this.start) * this.pos);
+ this.update();
+ }
+ return true;
+ }
+
+};
+
+hs.extend( hs.fx, {
+ step: {
+
+ opacity: function(fx){
+ hs.setStyles(fx.elem, { opacity: fx.now });
+ },
+
+ _default: function(fx){
+ try {
+ 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;
+ } catch (e) {}
+ }
+ }
+});
+
+hs.Outline = function (outlineType, onLoad) {
+ this.onLoad = onLoad;
+ this.outlineType = outlineType;
+ var v = hs.uaVersion, tr;
+
+ this.hasAlphaImageLoader = hs.ie && v >= 5.5 && v < 7;
+ if (!outlineType) {
+ if (onLoad) onLoad();
+ return;
+ }
+
+ hs.init();
+ this.table = hs.createElement(
+ 'table', {
+ cellSpacing: 0
+ }, {
+ visibility: 'hidden',
+ position: 'absolute',
+ borderCollapse: 'collapse',
+ width: 0
+ },
+ hs.container,
+ true
+ );
+ var tbody = hs.createElement('tbody', null, null, this.table, 1);
+
+ this.td = [];
+ for (var i = 0; i <= 8; i++) {
+ if (i % 3 == 0) tr = hs.createElement('tr', null, { height: 'auto' }, tbody, true);
+ this.td[i] = hs.createElement('td', null, null, tr, true);
+ var style = i != 4 ? { lineHeight: 0, fontSize: 0} : { position : 'relative' };
+ hs.setStyles(this.td[i], style);
+ }
+ this.td[4].className = outlineType +' highslide-outline';
+
+ this.preloadGraphic();
+};
+
+hs.Outline.prototype = {
+preloadGraphic : function () {
+ var src = hs.graphicsDir + (hs.outlinesDir || "outlines/")+ this.outlineType +".png";
+
+ var appendTo = hs.safari ? hs.container : null;
+ this.graphic = hs.createElement('img', null, { position: 'absolute',
+ top: '-9999px' }, appendTo, true); // for onload trigger
+
+ var pThis = this;
+ this.graphic.onload = function() { pThis.onGraphicLoad(); };
+
+ this.graphic.src = src;
+},
+
+onGraphicLoad : function () {
+ var o = this.offset = this.graphic.width / 4,
+ pos = [[0,0],[0,-4],[-2,0],[0,-8],0,[-2,-8],[0,-2],[0,-6],[-2,-2]],
+ dim = { height: (2*o) +'px', width: (2*o) +'px' };
+ for (var i = 0; i <= 8; i++) {
+ if (pos[i]) {
+ if (this.hasAlphaImageLoader) {
+ var w = (i == 1 || i == 7) ? '100%' : this.graphic.width +'px';
+ var div = hs.createElement('div', null, { width: '100%', height: '100%', position: 'relative', overflow: 'hidden'}, this.td[i], true);
+ hs.createElement ('div', null, {
+ filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale, src='"+ this.graphic.src + "')",
+ position: 'absolute',
+ width: w,
+ height: this.graphic.height +'px',
+ left: (pos[i][0]*o)+'px',
+ top: (pos[i][1]*o)+'px'
+ },
+ div,
+ true);
+ } else {
+ hs.setStyles(this.td[i], { background: 'url('+ this.graphic.src +') '+ (pos[i][0]*o)+'px '+(pos[i][1]*o)+'px'});
+ }
+
+ if (window.opera && (i == 3 || i ==5))
+ hs.createElement('div', null, dim, this.td[i], true);
+
+ hs.setStyles (this.td[i], dim);
+ }
+ }
+ this.graphic = null;
+ if (hs.pendingOutlines[this.outlineType]) hs.pendingOutlines[this.outlineType].destroy();
+ hs.pendingOutlines[this.outlineType] = this;
+ if (this.onLoad) this.onLoad();
+},
+
+setPosition : function (pos, offset, vis, dur, easing) {
+ var exp = this.exp,
+ stl = exp.wrapper.style,
+ offset = offset || 0,
+ pos = pos || {
+ x: exp.x.pos + offset,
+ y: exp.y.pos + offset,
+ w: exp.x.get('wsize') - 2 * offset,
+ h: exp.y.get('wsize') - 2 * offset
+ };
+ if (vis) this.table.style.visibility = (pos.h >= 4 * this.offset)
+ ? 'visible' : 'hidden';
+ hs.setStyles(this.table, {
+ left: (pos.x - this.offset) +'px',
+ top: (pos.y - this.offset) +'px',
+ width: (pos.w + 2 * this.offset) +'px'
+ });
+
+ pos.w -= 2 * this.offset;
+ pos.h -= 2 * this.offset;
+ hs.setStyles (this.td[4], {
+ width: pos.w >= 0 ? pos.w +'px' : 0,
+ height: pos.h >= 0 ? pos.h +'px' : 0
+ });
+ if (this.hasAlphaImageLoader) this.td[3].style.height
+ = this.td[5].style.height = this.td[4].style.height;
+
+},
+
+destroy : function(hide) {
+ if (hide) this.table.style.visibility = 'hidden';
+ else hs.discardElement(this.table);
+}
+};
+
+hs.Dimension = function(exp, dim) {
+ this.exp = exp;
+ this.dim = dim;
+ this.ucwh = dim == 'x' ? 'Width' : 'Height';
+ this.wh = this.ucwh.toLowerCase();
+ this.uclt = dim == 'x' ? 'Left' : 'Top';
+ this.lt = this.uclt.toLowerCase();
+ this.ucrb = dim == 'x' ? 'Right' : 'Bottom';
+ this.rb = this.ucrb.toLowerCase();
+ this.p1 = this.p2 = 0;
+};
+hs.Dimension.prototype = {
+get : function(key) {
+ switch (key) {
+ case 'loadingPos':
+ return this.tpos + this.tb + (this.t - hs.loading['offset'+ this.ucwh]) / 2;
+ case 'loadingPosXfade':
+ return this.pos + this.cb+ this.p1 + (this.size - hs.loading['offset'+ this.ucwh]) / 2;
+ case 'wsize':
+ return this.size + 2 * this.cb + this.p1 + this.p2;
+ case 'fitsize':
+ return this.clientSize - this.marginMin - this.marginMax;
+ case 'maxsize':
+ return this.get('fitsize') - 2 * this.cb - this.p1 - this.p2 ;
+ case 'opos':
+ return this.pos - (this.exp.outline ? this.exp.outline.offset : 0);
+ case 'osize':
+ return this.get('wsize') + (this.exp.outline ? 2*this.exp.outline.offset : 0);
+ case 'imgPad':
+ return this.imgSize ? Math.round((this.size - this.imgSize) / 2) : 0;
+
+ }
+},
+calcBorders: function() {
+ // correct for borders
+ this.cb = (this.exp.content['offset'+ this.ucwh] - this.t) / 2;
+
+ this.marginMax = hs['margin'+ this.ucrb];
+},
+calcThumb: function() {
+ this.t = this.exp.el[this.wh] ? parseInt(this.exp.el[this.wh]) :
+ this.exp.el['offset'+ this.ucwh];
+ this.tpos = this.exp.tpos[this.dim];
+ this.tb = (this.exp.el['offset'+ this.ucwh] - this.t) / 2;
+ if (this.tpos == 0 || this.tpos == -1) {
+ this.tpos = (hs.page[this.wh] / 2) + hs.page['scroll'+ this.uclt];
+ };
+},
+calcExpanded: function() {
+ var exp = this.exp;
+ this.justify = 'auto';
+
+ // get alignment
+ if (exp.align == 'center') this.justify = 'center';
+ else if (new RegExp(this.lt).test(exp.anchor)) this.justify = null;
+ else if (new RegExp(this.rb).test(exp.anchor)) this.justify = 'max';
+
+
+ // size and position
+ this.pos = this.tpos - this.cb + this.tb;
+
+ if (this.maxHeight && this.dim == 'x')
+ exp.maxWidth = Math.min(exp.maxWidth || this.full, exp.maxHeight * this.full / exp.y.full);
+
+ this.size = Math.min(this.full, exp['max'+ this.ucwh] || this.full);
+ this.minSize = exp.allowSizeReduction ?
+ Math.min(exp['min'+ this.ucwh], this.full) :this.full;
+ if (exp.isImage && exp.useBox) {
+ this.size = exp[this.wh];
+ this.imgSize = this.full;
+ }
+ if (this.dim == 'x' && hs.padToMinWidth) this.minSize = exp.minWidth;
+ this.target = exp['target'+ this.dim.toUpperCase()];
+ this.marginMin = hs['margin'+ this.uclt];
+ this.scroll = hs.page['scroll'+ this.uclt];
+ this.clientSize = hs.page[this.wh];
+},
+setSize: function(i) {
+ var exp = this.exp;
+ if (exp.isImage && (exp.useBox || hs.padToMinWidth)) {
+ this.imgSize = i;
+ this.size = Math.max(this.size, this.imgSize);
+ exp.content.style[this.lt] = this.get('imgPad')+'px';
+ } else
+ this.size = i;
+
+ exp.content.style[this.wh] = i +'px';
+ exp.wrapper.style[this.wh] = this.get('wsize') +'px';
+ if (exp.outline) exp.outline.setPosition();
+ if (exp.releaseMask) exp.releaseMask.style[this.wh] = i +'px';
+ if (this.dim == 'y' && exp.iDoc && exp.body.style.height != 'auto') try {
+ exp.iDoc.body.style.overflow = 'auto';
+ } catch (e) {}
+ if (exp.isHtml) {
+ var d = exp.scrollerDiv;
+ if (this.sizeDiff === undefined)
+ this.sizeDiff = exp.innerContent['offset'+ this.ucwh] - d['offset'+ this.ucwh];
+ d.style[this.wh] = (this.size - this.sizeDiff) +'px';
+
+ if (this.dim == 'x') exp.mediumContent.style.width = 'auto';
+ if (exp.body) exp.body.style[this.wh] = 'auto';
+ }
+ if (this.dim == 'x' && exp.overlayBox) exp.sizeOverlayBox(true);
+ if (this.dim == 'x' && exp.slideshow && exp.isImage) {
+ if (i == this.full) exp.slideshow.disable('full-expand');
+ else exp.slideshow.enable('full-expand');
+ }
+},
+setPos: function(i) {
+ this.pos = i;
+ this.exp.wrapper.style[this.lt] = i +'px';
+
+ if (this.exp.outline) this.exp.outline.setPosition();
+
+}
+};
+
+hs.Expander = function(a, params, custom, contentType) {
+ if (document.readyState && hs.ie && !hs.isReady) {
+ hs.addEventListener(document, 'ready', function() {
+ new hs.Expander(a, params, custom, contentType);
+ });
+ return;
+ }
+ this.a = a;
+ this.custom = custom;
+ this.contentType = contentType || 'image';
+ this.isHtml = (contentType == 'html');
+ this.isImage = !this.isHtml;
+
+ hs.continuePreloading = false;
+ this.overlays = [];
+ this.last = hs.last;
+ hs.last = null;
+ hs.init();
+ var key = this.key = hs.expanders.length;
+ // override inline parameters
+ for (var i = 0; i < hs.overrides.length; i++) {
+ var name = hs.overrides[i];
+ this[name] = params && typeof params[name] != 'undefined' ?
+ params[name] : hs[name];
+ }
+ if (!this.src) this.src = a.href;
+
+ // get thumb
+ var el = (params && params.thumbnailId) ? hs.$(params.thumbnailId) : a;
+ el = this.thumb = el.getElementsByTagName('img')[0] || el;
+ this.thumbsUserSetId = el.id || a.id;
+ if (!hs.fireEvent(this, 'onInit')) return true;
+
+ // check if already open
+ for (var i = 0; i < hs.expanders.length; i++) {
+ if (hs.expanders[i] && hs.expanders[i].a == a
+ && !(this.last && this.transitions[1] == 'crossfade')) {
+ hs.expanders[i].focus();
+ return false;
+ }
+ }
+
+ // cancel other
+ if (!hs.allowSimultaneousLoading) for (var i = 0; i < hs.expanders.length; i++) {
+ if (hs.expanders[i] && hs.expanders[i].thumb != el && !hs.expanders[i].onLoadStarted) {
+ hs.expanders[i].cancelLoading();
+ }
+ }
+ hs.expanders[key] = this;
+ if (!hs.allowMultipleInstances && !hs.upcoming) {
+ if (hs.expanders[key-1]) hs.expanders[key-1].close();
+ if (typeof hs.focusKey != 'undefined' && hs.expanders[hs.focusKey])
+ hs.expanders[hs.focusKey].close();
+ }
+
+ // initiate metrics
+ this.el = el;
+ this.tpos = this.pageOrigin || hs.getPosition(el);
+ hs.getPageSize();
+ var x = this.x = new hs.Dimension(this, 'x');
+ x.calcThumb();
+ var y = this.y = new hs.Dimension(this, 'y');
+ y.calcThumb();
+ if (/area/i.test(el.tagName)) this.getImageMapAreaCorrection(el);
+ this.wrapper = hs.createElement(
+ 'div', {
+ id: 'highslide-wrapper-'+ this.key,
+ className: 'highslide-wrapper '+ this.wrapperClassName
+ }, {
+ visibility: 'hidden',
+ position: 'absolute',
+ zIndex: hs.zIndexCounter += 2
+ }, null, true );
+
+ this.wrapper.onmouseover = this.wrapper.onmouseout = hs.wrapperMouseHandler;
+ if (this.contentType == 'image' && this.outlineWhileAnimating == 2)
+ this.outlineWhileAnimating = 0;
+
+ // get the outline
+ if (!this.outlineType
+ || (this.last && this.isImage && this.transitions[1] == 'crossfade')) {
+ this[this.contentType +'Create']();
+
+ } else if (hs.pendingOutlines[this.outlineType]) {
+ this.connectOutline();
+ this[this.contentType +'Create']();
+
+ } else {
+ this.showLoading();
+ var exp = this;
+ new hs.Outline(this.outlineType,
+ function () {
+ exp.connectOutline();
+ exp[exp.contentType +'Create']();
+ }
+ );
+ }
+ return true;
+};
+
+hs.Expander.prototype = {
+error : function(e) {
+ if (hs.debug) alert ('Line '+ e.lineNumber +': '+ e.message);
+ else window.location.href = this.src;
+},
+
+connectOutline : function() {
+ var outline = this.outline = hs.pendingOutlines[this.outlineType];
+ outline.exp = this;
+ outline.table.style.zIndex = this.wrapper.style.zIndex - 1;
+ hs.pendingOutlines[this.outlineType] = null;
+},
+
+showLoading : function() {
+ if (this.onLoadStarted || this.loading) return;
+
+ this.loading = hs.loading;
+ var exp = this;
+ this.loading.onclick = function() {
+ exp.cancelLoading();
+ };
+
+
+ if (!hs.fireEvent(this, 'onShowLoading')) return;
+ var exp = this,
+ l = this.x.get('loadingPos') +'px',
+ t = this.y.get('loadingPos') +'px';
+ if (!tgt && this.last && this.transitions[1] == 'crossfade')
+ var tgt = this.last;
+ if (tgt) {
+ l = tgt.x.get('loadingPosXfade') +'px';
+ t = tgt.y.get('loadingPosXfade') +'px';
+ this.loading.style.zIndex = hs.zIndexCounter++;
+ }
+ setTimeout(function () {
+ if (exp.loading) hs.setStyles(exp.loading, { left: l, top: t, zIndex: hs.zIndexCounter++ })}
+ , 100);
+},
+
+imageCreate : function() {
+ var exp = this;
+
+ var img = document.createElement('img');
+ this.content = img;
+ img.onload = function () {
+ if (hs.expanders[exp.key]) exp.contentLoaded();
+ };
+ if (hs.blockRightClick) img.oncontextmenu = function() { return false; };
+ img.className = 'highslide-image';
+ hs.setStyles(img, {
+ visibility: 'hidden',
+ display: 'block',
+ position: 'absolute',
+ maxWidth: '9999px',
+ zIndex: 3
+ });
+ img.title = hs.lang.restoreTitle;
+ if (hs.safari) hs.container.appendChild(img);
+ if (hs.ie && hs.flushImgSize) img.src = null;
+ img.src = this.src;
+
+ this.showLoading();
+},
+
+htmlCreate : function () {
+ if (!hs.fireEvent(this, 'onBeforeGetContent')) return;
+
+ this.content = hs.getCacheBinding(this.a);
+ if (!this.content)
+ this.content = hs.getNode(this.contentId);
+ if (!this.content)
+ this.content = hs.getSelfRendered();
+ this.getInline(['maincontent']);
+ if (this.maincontent) {
+ var body = hs.getElementByClass(this.content, 'div', 'highslide-body');
+ if (body) body.appendChild(this.maincontent);
+ this.maincontent.style.display = 'block';
+ }
+ hs.fireEvent(this, 'onAfterGetContent');
+
+ var innerContent = this.innerContent = this.content;
+
+ if (/(swf|iframe)/.test(this.objectType)) this.setObjContainerSize(innerContent);
+
+ // the content tree
+ hs.container.appendChild(this.wrapper);
+ hs.setStyles( this.wrapper, {
+ position: 'static',
+ padding: '0 '+ hs.marginRight +'px 0 '+ hs.marginLeft +'px'
+ });
+ this.content = hs.createElement(
+ 'div', {
+ className: 'highslide-html'
+ }, {
+ position: 'relative',
+ zIndex: 3,
+ height: 0,
+ overflow: 'hidden'
+ },
+ this.wrapper
+ );
+ this.mediumContent = hs.createElement('div', null, null, this.content, 1);
+ this.mediumContent.appendChild(innerContent);
+
+ hs.setStyles (innerContent, {
+ position: 'relative',
+ display: 'block',
+ direction: hs.lang.cssDirection || ''
+ });
+ if (this.width) innerContent.style.width = this.width +'px';
+ if (this.height) hs.setStyles(innerContent, {
+ height: this.height +'px',
+ overflow: 'hidden'
+ });
+ if (innerContent.offsetWidth < this.minWidth)
+ innerContent.style.width = this.minWidth +'px';
+
+
+
+ if (this.objectType == 'ajax' && !hs.getCacheBinding(this.a)) {
+ this.showLoading();
+ var exp = this;
+ var ajax = new hs.Ajax(this.a, innerContent);
+ ajax.src = this.src;
+ ajax.onLoad = function () { if (hs.expanders[exp.key]) exp.contentLoaded(); };
+ ajax.onError = function () { location.href = exp.src; };
+ ajax.run();
+ }
+ else
+
+ if (this.objectType == 'iframe' && this.objectLoadTime == 'before') {
+ this.writeExtendedContent();
+ }
+ else
+ this.contentLoaded();
+},
+
+contentLoaded : function() {
+ try {
+ if (!this.content) return;
+ this.content.onload = null;
+ if (this.onLoadStarted) return;
+ else this.onLoadStarted = true;
+
+ var x = this.x, y = this.y;
+
+ if (this.loading) {
+ hs.setStyles(this.loading, { top: '-9999px' });
+ this.loading = null;
+ hs.fireEvent(this, 'onHideLoading');
+ }
+ if (this.isImage) {
+ x.full = this.content.width;
+ y.full = this.content.height;
+
+ hs.setStyles(this.content, {
+ width: x.t +'px',
+ height: y.t +'px'
+ });
+ this.wrapper.appendChild(this.content);
+ hs.container.appendChild(this.wrapper);
+ } else if (this.htmlGetSize) this.htmlGetSize();
+
+ x.calcBorders();
+ y.calcBorders();
+
+ hs.setStyles (this.wrapper, {
+ left: (x.tpos + x.tb - x.cb) +'px',
+ top: (y.tpos + x.tb - y.cb) +'px'
+ });
+
+
+ this.initSlideshow();
+ this.getOverlays();
+
+ var ratio = x.full / y.full;
+ x.calcExpanded();
+ this.justify(x);
+
+ y.calcExpanded();
+ this.justify(y);
+ if (this.isHtml) this.htmlSizeOperations();
+ if (this.overlayBox) this.sizeOverlayBox(0, 1);
+
+
+ if (this.allowSizeReduction) {
+ if (this.isImage)
+ this.correctRatio(ratio);
+ else this.fitOverlayBox();
+ var ss = this.slideshow;
+ if (ss && this.last && ss.controls && ss.fixedControls) {
+ var pos = ss.overlayOptions.position || '', p;
+ for (var dim in hs.oPos) for (var i = 0; i < 5; i++) {
+ p = this[dim];
+ if (pos.match(hs.oPos[dim][i])) {
+ p.pos = this.last[dim].pos
+ + (this.last[dim].p1 - p.p1)
+ + (this.last[dim].size - p.size) * [0, 0, .5, 1, 1][i];
+ if (ss.fixedControls == 'fit') {
+ if (p.pos + p.size + p.p1 + p.p2 > p.scroll + p.clientSize - p.marginMax)
+ p.pos = p.scroll + p.clientSize - p.size - p.marginMin - p.marginMax - p.p1 - p.p2;
+ if (p.pos < p.scroll + p.marginMin) p.pos = p.scroll + p.marginMin;
+ }
+ }
+ }
+ }
+ if (this.isImage && this.x.full > (this.x.imgSize || this.x.size)) {
+ this.createFullExpand();
+ if (this.overlays.length == 1) this.sizeOverlayBox();
+ }
+ }
+ this.show();
+
+ } catch (e) {
+ this.error(e);
+ }
+},
+
+
+setObjContainerSize : function(parent, auto) {
+ var c = hs.getElementByClass(parent, 'DIV', 'highslide-body');
+ if (/(iframe|swf)/.test(this.objectType)) {
+ if (this.objectWidth) c.style.width = this.objectWidth +'px';
+ if (this.objectHeight) c.style.height = this.objectHeight +'px';
+ }
+},
+
+writeExtendedContent : function () {
+ if (this.hasExtendedContent) return;
+ var exp = this;
+ this.body = hs.getElementByClass(this.innerContent, 'DIV', 'highslide-body');
+ if (this.objectType == 'iframe') {
+ this.showLoading();
+ var ruler = hs.clearing.cloneNode(1);
+ this.body.appendChild(ruler);
+ this.newWidth = this.innerContent.offsetWidth;
+ if (!this.objectWidth) this.objectWidth = ruler.offsetWidth;
+ var hDiff = this.innerContent.offsetHeight - this.body.offsetHeight,
+ h = this.objectHeight || hs.page.height - hDiff - hs.marginTop - hs.marginBottom,
+ onload = this.objectLoadTime == 'before' ?
+ ' onload="if (hs.expanders['+ this.key +']) hs.expanders['+ this.key +'].contentLoaded()" ' : '';
+ this.body.innerHTML += '<iframe name="hs'+ (new Date()).getTime() +'" frameborder="0" key="'+ this.key +'" '
+ +' style="width:'+ this.objectWidth +'px; height:'+ h +'px" '
+ + onload +' src="'+ this.src +'" ></iframe>';
+ this.ruler = this.body.getElementsByTagName('div')[0];
+ this.iframe = this.body.getElementsByTagName('iframe')[0];
+
+ if (this.objectLoadTime == 'after') this.correctIframeSize();
+
+ }
+ if (this.objectType == 'swf') {
+ this.body.id = this.body.id || 'hs-flash-id-' + this.key;
+ var a = this.swfOptions;
+ if (!a.params) a.params = {};
+ if (typeof a.params.wmode == 'undefined') a.params.wmode = 'transparent';
+ if (swfobject) swfobject.embedSWF(this.src, this.body.id, this.objectWidth, this.objectHeight,
+ a.version || '7', a.expressInstallSwfurl, a.flashvars, a.params, a.attributes);
+ }
+ this.hasExtendedContent = true;
+},
+htmlGetSize : function() {
+ if (this.iframe && !this.objectHeight) { // loadtime before
+ this.iframe.style.height = this.body.style.height = this.getIframePageHeight() +'px';
+ }
+ this.innerContent.appendChild(hs.clearing);
+ if (!this.x.full) this.x.full = this.innerContent.offsetWidth;
+ this.y.full = this.innerContent.offsetHeight;
+ this.innerContent.removeChild(hs.clearing);
+ if (hs.ie && this.newHeight > parseInt(this.innerContent.currentStyle.height)) { // ie css bug
+ this.newHeight = parseInt(this.innerContent.currentStyle.height);
+ }
+ hs.setStyles( this.wrapper, { position: 'absolute', padding: '0'});
+ hs.setStyles( this.content, { width: this.x.t +'px', height: this.y.t +'px'});
+
+},
+
+getIframePageHeight : function() {
+ var h;
+ try {
+ var doc = this.iDoc = this.iframe.contentDocument || this.iframe.contentWindow.document;
+ var clearing = doc.createElement('div');
+ clearing.style.clear = 'both';
+ doc.body.appendChild(clearing);
+ h = clearing.offsetTop;
+ if (hs.ie) h += parseInt(doc.body.currentStyle.marginTop)
+ + parseInt(doc.body.currentStyle.marginBottom) - 1;
+ } catch (e) { // other domain
+ h = 300;
+ }
+ return h;
+},
+correctIframeSize : function () {
+ var wDiff = this.innerContent.offsetWidth - this.ruler.offsetWidth;
+ hs.discardElement(this.ruler);
+ if (wDiff < 0) wDiff = 0;
+
+ var hDiff = this.innerContent.offsetHeight - this.iframe.offsetHeight;
+ if (this.iDoc && !this.objectHeight && !this.height && this.y.size == this.y.full) try {
+ this.iDoc.body.style.overflow = 'hidden';
+ } catch (e) {}
+ hs.setStyles(this.iframe, {
+ width: Math.abs(this.x.size - wDiff) +'px',
+ height: Math.abs(this.y.size - hDiff) +'px'
+ });
+ hs.setStyles(this.body, {
+ width: this.iframe.style.width,
+ height: this.iframe.style.height
+ });
+
+ this.scrollingContent = this.iframe;
+ this.scrollerDiv = this.scrollingContent;
+
+},
+htmlSizeOperations : function () {
+
+ this.setObjContainerSize(this.innerContent);
+
+
+ if (this.objectType == 'swf' && this.objectLoadTime == 'before') this.writeExtendedContent();
+
+ // handle minimum size
+ if (this.x.size < this.x.full && !this.allowWidthReduction) this.x.size = this.x.full;
+ if (this.y.size < this.y.full && !this.allowHeightReduction) this.y.size = this.y.full;
+ this.scrollerDiv = this.innerContent;
+ hs.setStyles(this.mediumContent, {
+ position: 'relative',
+ width: this.x.size +'px'
+ });
+ hs.setStyles(this.innerContent, {
+ border: 'none',
+ width: 'auto',
+ height: 'auto'
+ });
+ var node = hs.getElementByClass(this.innerContent, 'DIV', 'highslide-body');
+ if (node && !/(iframe|swf)/.test(this.objectType)) {
+ var cNode = node; // wrap to get true size
+ node = hs.createElement(cNode.nodeName, null, {overflow: 'hidden'}, null, true);
+ cNode.parentNode.insertBefore(node, cNode);
+ node.appendChild(hs.clearing); // IE6
+ node.appendChild(cNode);
+
+ var wDiff = this.innerContent.offsetWidth - node.offsetWidth;
+ var hDiff = this.innerContent.offsetHeight - node.offsetHeight;
+ node.removeChild(hs.clearing);
+
+ var kdeBugCorr = hs.safari || navigator.vendor == 'KDE' ? 1 : 0; // KDE repainting bug
+ hs.setStyles(node, {
+ width: (this.x.size - wDiff - kdeBugCorr) +'px',
+ height: (this.y.size - hDiff) +'px',
+ overflow: 'auto',
+ position: 'relative'
+ }
+ );
+ if (kdeBugCorr && cNode.offsetHeight > node.offsetHeight) {
+ node.style.width = (parseInt(node.style.width) + kdeBugCorr) + 'px';
+ }
+ this.scrollingContent = node;
+ this.scrollerDiv = this.scrollingContent;
+ }
+ if (this.iframe && this.objectLoadTime == 'before') this.correctIframeSize();
+ if (!this.scrollingContent && this.y.size < this.mediumContent.offsetHeight) this.scrollerDiv = this.content;
+
+ if (this.scrollerDiv == this.content && !this.allowWidthReduction && !/(iframe|swf)/.test(this.objectType)) {
+ this.x.size += 17; // room for scrollbars
+ }
+ if (this.scrollerDiv && this.scrollerDiv.offsetHeight > this.scrollerDiv.parentNode.offsetHeight) {
+ setTimeout("try { hs.expanders["+ this.key +"].scrollerDiv.style.overflow = 'auto'; } catch(e) {}",
+ hs.expandDuration);
+ }
+},
+
+getImageMapAreaCorrection : function(area) {
+ var c = area.coords.split(',');
+ for (var i = 0; i < c.length; i++) c[i] = parseInt(c[i]);
+
+ if (area.shape.toLowerCase() == 'circle') {
+ this.x.tpos += c[0] - c[2];
+ this.y.tpos += c[1] - c[2];
+ this.x.t = this.y.t = 2 * c[2];
+ } else {
+ var maxX, maxY, minX = maxX = c[0], minY = maxY = c[1];
+ for (var i = 0; i < c.length; i++) {
+ if (i % 2 == 0) {
+ minX = Math.min(minX, c[i]);
+ maxX = Math.max(maxX, c[i]);
+ } else {
+ minY = Math.min(minY, c[i]);
+ maxY = Math.max(maxY, c[i]);
+ }
+ }
+ this.x.tpos += minX;
+ this.x.t = maxX - minX;
+ this.y.tpos += minY;
+ this.y.t = maxY - minY;
+ }
+},
+justify : function (p, moveOnly) {
+ var tgtArr, tgt = p.target, dim = p == this.x ? 'x' : 'y';
+
+ if (tgt && tgt.match(/ /)) {
+ tgtArr = tgt.split(' ');
+ tgt = tgtArr[0];
+ }
+ if (tgt && hs.$(tgt)) {
+ p.pos = hs.getPosition(hs.$(tgt))[dim];
+ if (tgtArr && tgtArr[1] && tgtArr[1].match(/^[-]?[0-9]+px$/))
+ p.pos += parseInt(tgtArr[1]);
+ if (p.size < p.minSize) p.size = p.minSize;
+
+ } else if (p.justify == 'auto' || p.justify == 'center') {
+
+ var hasMovedMin = false;
+
+ var allowReduce = p.exp.allowSizeReduction;
+ if (p.justify == 'center')
+ p.pos = Math.round(p.scroll + (p.clientSize + p.marginMin - p.marginMax - p.get('wsize')) / 2);
+ else
+ p.pos = Math.round(p.pos - ((p.get('wsize') - p.t) / 2));
+ if (p.pos < p.scroll + p.marginMin) {
+ p.pos = p.scroll + p.marginMin;
+ hasMovedMin = true;
+ }
+ if (!moveOnly && p.size < p.minSize) {
+ p.size = p.minSize;
+ allowReduce = false;
+ }
+ if (p.pos + p.get('wsize') > p.scroll + p.clientSize - p.marginMax) {
+ if (!moveOnly && hasMovedMin && allowReduce) {
+ p.size = Math.min(p.size, p.get(dim == 'y' ? 'fitsize' : 'maxsize'));
+ } else if (p.get('wsize') < p.get('fitsize')) {
+ p.pos = p.scroll + p.clientSize - p.marginMax - p.get('wsize');
+ } else { // image larger than viewport
+ p.pos = p.scroll + p.marginMin;
+ if (!moveOnly && allowReduce) p.size = p.get(dim == 'y' ? 'fitsize' : 'maxsize');
+ }
+ }
+
+ if (!moveOnly && p.size < p.minSize) {
+ p.size = p.minSize;
+ allowReduce = false;
+ }
+
+
+ } else if (p.justify == 'max') {
+ p.pos = Math.floor(p.pos - p.size + p.t);
+ }
+
+
+ if (p.pos < p.marginMin) {
+ var tmpMin = p.pos;
+ p.pos = p.marginMin;
+
+ if (allowReduce && !moveOnly) p.size = p.size - (p.pos - tmpMin);
+
+ }
+},
+
+correctRatio : function(ratio) {
+ var x = this.x,
+ y = this.y,
+ changed = false,
+ xSize = Math.min(x.full, x.size),
+ ySize = Math.min(y.full, y.size),
+ useBox = (this.useBox || hs.padToMinWidth);
+
+ if (xSize / ySize > ratio) { // width greater
+ xSize = ySize * ratio;
+ if (xSize < x.minSize) { // below minWidth
+ xSize = x.minSize;
+ ySize = xSize / ratio;
+ }
+ changed = true;
+
+ } else if (xSize / ySize < ratio) { // height greater
+ ySize = xSize / ratio;
+ changed = true;
+ }
+
+ if (hs.padToMinWidth && x.full < x.minSize) {
+ x.imgSize = x.full;
+ y.size = y.imgSize = y.full;
+ } else if (this.useBox) {
+ x.imgSize = xSize;
+ y.imgSize = ySize;
+ } else {
+ x.size = xSize;
+ y.size = ySize;
+ }
+ changed = this.fitOverlayBox(useBox ? null : ratio, changed);
+ if (useBox && y.size < y.imgSize) {
+ y.imgSize = y.size;
+ x.imgSize = y.size * ratio;
+ }
+ if (changed || useBox) {
+ x.pos = x.tpos - x.cb + x.tb;
+ x.minSize = x.size;
+ this.justify(x, true);
+
+ y.pos = y.tpos - y.cb + y.tb;
+ y.minSize = y.size;
+ this.justify(y, true);
+ if (this.overlayBox) this.sizeOverlayBox();
+ }
+},
+fitOverlayBox : function(ratio, changed) {
+ var x = this.x, y = this.y;
+ if (this.overlayBox && (this.isImage || this.allowHeightReduction)) {
+ while (y.size > this.minHeight && x.size > this.minWidth
+ && y.get('wsize') > y.get('fitsize')) {
+ y.size -= 10;
+ if (ratio) x.size = y.size * ratio;
+ this.sizeOverlayBox(0, 1);
+ changed = true;
+ }
+ }
+ return changed;
+},
+
+reflow : function () {
+ if (this.scrollerDiv) {
+ var h = /iframe/i.test(this.scrollerDiv.tagName) ? (this.getIframePageHeight() + 1) +'px' : 'auto';
+ if (this.body) this.body.style.height = h;
+ this.scrollerDiv.style.height = h;
+ this.y.setSize(this.innerContent.offsetHeight);
+ }
+},
+
+show : function () {
+ var x = this.x, y = this.y;
+ this.doShowHide('hidden');
+ hs.fireEvent(this, 'onBeforeExpand');
+ if (this.slideshow && this.slideshow.thumbstrip) this.slideshow.thumbstrip.selectThumb();
+
+ // Apply size change
+ this.changeSize(
+ 1, {
+ wrapper: {
+ width : x.get('wsize'),
+ height : y.get('wsize'),
+ left: x.pos,
+ top: y.pos
+ },
+ content: {
+ left: x.p1 + x.get('imgPad'),
+ top: y.p1 + y.get('imgPad'),
+ width:x.imgSize ||x.size,
+ height:y.imgSize ||y.size
+ }
+ },
+ hs.expandDuration
+ );
+},
+
+changeSize : function(up, to, dur) {
+ // transition
+ var trans = this.transitions,
+ other = up ? (this.last ? this.last.a : null) : hs.upcoming,
+ t = (trans[1] && other
+ && hs.getParam(other, 'transitions')[1] == trans[1]) ?
+ trans[1] : trans[0];
+
+ if (this[t] && t != 'expand') {
+ this[t](up, to);
+ return;
+ }
+
+ if (this.outline && !this.outlineWhileAnimating) {
+ if (up) this.outline.setPosition();
+ else this.outline.destroy(
+ (this.isHtml && this.preserveContent));
+ }
+
+
+ if (!up) this.destroyOverlays();
+
+ var exp = this,
+ x = exp.x,
+ y = exp.y,
+ easing = this.easing;
+ if (!up) easing = this.easingClose || easing;
+ var after = up ?
+ function() {
+
+ if (exp.outline) exp.outline.table.style.visibility = "visible";
+ setTimeout(function() {
+ exp.afterExpand();
+ }, 50);
+ } :
+ function() {
+ exp.afterClose();
+ };
+ if (up) hs.setStyles( this.wrapper, {
+ width: x.t +'px',
+ height: y.t +'px'
+ });
+ if (up && this.isHtml) {
+ hs.setStyles(this.wrapper, {
+ left: (x.tpos - x.cb + x.tb) +'px',
+ top: (y.tpos - y.cb + y.tb) +'px'
+ });
+ }
+ if (this.fadeInOut) {
+ hs.setStyles(this.wrapper, { opacity: up ? 0 : 1 });
+ hs.extend(to.wrapper, { opacity: up });
+ }
+ hs.animate( this.wrapper, to.wrapper, {
+ duration: dur,
+ easing: easing,
+ step: function(val, args) {
+ if (exp.outline && exp.outlineWhileAnimating && args.prop == 'top') {
+ var fac = up ? args.pos : 1 - args.pos;
+ var pos = {
+ w: x.t + (x.get('wsize') - x.t) * fac,
+ h: y.t + (y.get('wsize') - y.t) * fac,
+ x: x.tpos + (x.pos - x.tpos) * fac,
+ y: y.tpos + (y.pos - y.tpos) * fac
+ };
+ exp.outline.setPosition(pos, 0, 1);
+ }
+ if (exp.isHtml) {
+ if (args.prop == 'left')
+ exp.mediumContent.style.left = (x.pos - val) +'px';
+ if (args.prop == 'top')
+ exp.mediumContent.style.top = (y.pos - val) +'px';
+ }
+ }
+ });
+ hs.animate( this.content, to.content, dur, easing, after);
+ if (up) {
+ this.wrapper.style.visibility = 'visible';
+ this.content.style.visibility = 'visible';
+ if (this.isHtml) this.innerContent.style.visibility = 'visible';
+ }
+},
+
+
+
+fade : function(up, to) {
+ this.outlineWhileAnimating = false;
+ var exp = this, t = up ? hs.expandDuration : 0;
+
+ if (up) {
+ hs.animate(this.wrapper, to.wrapper, 0);
+ hs.setStyles(this.wrapper, { opacity: 0, visibility: 'visible' });
+ hs.animate(this.content, to.content, 0);
+ this.content.style.visibility = 'visible';
+
+ hs.animate(this.wrapper, { opacity: 1 }, t, null,
+ function() { exp.afterExpand(); });
+ }
+
+ if (this.outline) {
+ this.outline.table.style.zIndex = this.wrapper.style.zIndex;
+ var dir = up || -1,
+ offset = this.outline.offset,
+ startOff = up ? 3 : offset,
+ endOff = up? offset : 3;
+ for (var i = startOff; dir * i <= dir * endOff; i += dir, t += 25) {
+ (function() {
+ var o = up ? endOff - i : startOff - i;
+ setTimeout(function() {
+ exp.outline.setPosition(0, o, 1);
+ }, t);
+ })();
+ }
+ }
+
+
+ if (up) {}//setTimeout(function() { exp.afterExpand(); }, t+50);
+ else {
+ setTimeout( function() {
+ if (exp.outline) exp.outline.destroy(exp.preserveContent);
+
+ exp.destroyOverlays();
+
+ hs.animate( exp.wrapper, { opacity: 0 }, hs.restoreDuration, null, function(){
+ exp.afterClose();
+ });
+ }, t);
+ }
+},
+crossfade : function (up, to, from) {
+ if (!up) return;
+ var exp = this,
+ last = this.last,
+ x = this.x,
+ y = this.y,
+ lastX = last.x,
+ lastY = last.y,
+ wrapper = this.wrapper,
+ content = this.content,
+ overlayBox = this.overlayBox;
+ hs.removeEventListener(document, 'mousemove', hs.dragHandler);
+
+ hs.setStyles(content, {
+ width: (x.imgSize || x.size) +'px',
+ height: (y.imgSize || y.size) +'px'
+ });
+ if (overlayBox) overlayBox.style.overflow = 'visible';
+ this.outline = last.outline;
+ if (this.outline) this.outline.exp = exp;
+ last.outline = null;
+ var fadeBox = hs.createElement('div', {
+ className: 'highslide-'+ this.contentType
+ }, {
+ position: 'absolute',
+ zIndex: 4,
+ overflow: 'hidden',
+ display: 'none'
+ }
+ );
+ var names = { oldImg: last, newImg: this };
+ for (var n in names) {
+ this[n] = names[n].content.cloneNode(1);
+ hs.setStyles(this[n], {
+ position: 'absolute',
+ border: 0,
+ visibility: 'visible'
+ });
+ fadeBox.appendChild(this[n]);
+ }
+ wrapper.appendChild(fadeBox);
+ if (this.isHtml) hs.setStyles(this.mediumContent, {
+ left: 0,
+ top: 0
+ });
+ if (overlayBox) {
+ overlayBox.className = '';
+ wrapper.appendChild(overlayBox);
+ }
+ fadeBox.style.display = '';
+ last.content.style.display = 'none';
+
+
+ if (hs.safari) {
+ var match = navigator.userAgent.match(/Safari\/([0-9]{3})/);
+ if (match && parseInt(match[1]) < 525) this.wrapper.style.visibility = 'visible';
+ }
+ hs.animate(wrapper, {
+ width: x.size
+ }, {
+ duration: hs.transitionDuration,
+ step: function(val, args) {
+ var pos = args.pos,
+ invPos = 1 - pos;
+ var prop,
+ size = {},
+ props = ['pos', 'size', 'p1', 'p2'];
+ for (var n in props) {
+ prop = props[n];
+ size['x'+ prop] = Math.round(invPos * lastX[prop] + pos * x[prop]);
+ size['y'+ prop] = Math.round(invPos * lastY[prop] + pos * y[prop]);
+ size.ximgSize = Math.round(
+ invPos * (lastX.imgSize || lastX.size) + pos * (x.imgSize || x.size));
+ size.ximgPad = Math.round(invPos * lastX.get('imgPad') + pos * x.get('imgPad'));
+ size.yimgSize = Math.round(
+ invPos * (lastY.imgSize || lastY.size) + pos * (y.imgSize || y.size));
+ size.yimgPad = Math.round(invPos * lastY.get('imgPad') + pos * y.get('imgPad'));
+ }
+ if (exp.outline) exp.outline.setPosition({
+ x: size.xpos,
+ y: size.ypos,
+ w: size.xsize + size.xp1 + size.xp2 + 2 * x.cb,
+ h: size.ysize + size.yp1 + size.yp2 + 2 * y.cb
+ });
+ last.wrapper.style.clip = 'rect('
+ + (size.ypos - lastY.pos)+'px, '
+ + (size.xsize + size.xp1 + size.xp2 + size.xpos + 2 * lastX.cb - lastX.pos) +'px, '
+ + (size.ysize + size.yp1 + size.yp2 + size.ypos + 2 * lastY.cb - lastY.pos) +'px, '
+ + (size.xpos - lastX.pos)+'px)';
+
+ hs.setStyles(content, {
+ top: (size.yp1 + y.get('imgPad')) +'px',
+ left: (size.xp1 + x.get('imgPad')) +'px',
+ marginTop: (y.pos - size.ypos) +'px',
+ marginLeft: (x.pos - size.xpos) +'px'
+ });
+ hs.setStyles(wrapper, {
+ top: size.ypos +'px',
+ left: size.xpos +'px',
+ width: (size.xp1 + size.xp2 + size.xsize + 2 * x.cb)+ 'px',
+ height: (size.yp1 + size.yp2 + size.ysize + 2 * y.cb) + 'px'
+ });
+ hs.setStyles(fadeBox, {
+ width: (size.ximgSize || size.xsize) + 'px',
+ height: (size.yimgSize || size.ysize) +'px',
+ left: (size.xp1 + size.ximgPad) +'px',
+ top: (size.yp1 + size.yimgPad) +'px',
+ visibility: 'visible'
+ });
+
+ hs.setStyles(exp.oldImg, {
+ top: (lastY.pos - size.ypos + lastY.p1 - size.yp1 + lastY.get('imgPad') - size.yimgPad)+'px',
+ left: (lastX.pos - size.xpos + lastX.p1 - size.xp1 + lastX.get('imgPad') - size.ximgPad)+'px'
+ });
+
+ hs.setStyles(exp.newImg, {
+ opacity: pos,
+ top: (y.pos - size.ypos + y.p1 - size.yp1 + y.get('imgPad') - size.yimgPad) +'px',
+ left: (x.pos - size.xpos + x.p1 - size.xp1 + x.get('imgPad') - size.ximgPad) +'px'
+ });
+ if (overlayBox) hs.setStyles(overlayBox, {
+ width: size.xsize + 'px',
+ height: size.ysize +'px',
+ left: (size.xp1 + x.cb) +'px',
+ top: (size.yp1 + y.cb) +'px'
+ });
+ },
+ complete: function () {
+ wrapper.style.visibility = content.style.visibility = 'visible';
+ content.style.display = 'block';
+ hs.discardElement(fadeBox);
+ exp.afterExpand();
+ last.afterClose();
+ exp.last = null;
+ }
+
+ });
+},
+reuseOverlay : function(o, el) {
+ if (!this.last) return false;
+ for (var i = 0; i < this.last.overlays.length; i++) {
+ var oDiv = hs.$('hsId'+ this.last.overlays[i]);
+ if (oDiv && oDiv.hsId == o.hsId) {
+ this.genOverlayBox();
+ oDiv.reuse = this.key;
+ hs.push(this.overlays, this.last.overlays[i]);
+ return true;
+ }
+ }
+ return false;
+},
+
+
+afterExpand : function() {
+ this.isExpanded = true;
+
+ this.a.className += ' highslide-active-anchor';
+ this.focus();
+
+ if (this.isHtml && this.objectLoadTime == 'after') this.writeExtendedContent();
+ if (this.iframe) {
+ try {
+ var exp = this,
+ doc = this.iframe.contentDocument || this.iframe.contentWindow.document;
+ hs.addEventListener(doc, 'mousedown', function () {
+ if (hs.focusKey != exp.key) exp.focus();
+ });
+ } catch(e) {}
+ if (hs.ie && typeof this.isClosing != 'boolean') // first open
+ this.iframe.style.width = (this.objectWidth - 1) +'px'; // hasLayout
+ }
+ if (this.dimmingOpacity) hs.dim(this);
+ if (hs.upcoming && hs.upcoming == this.a) hs.upcoming = null;
+ this.prepareNextOutline();
+ var p = hs.page, mX = hs.mouse.x + p.scrollLeft, mY = hs.mouse.y + p.scrollTop;
+ this.mouseIsOver = this.x.pos < mX && mX < this.x.pos + this.x.get('wsize')
+ && this.y.pos < mY && mY < this.y.pos + this.y.get('wsize');
+ if (this.overlayBox) this.showOverlays();
+ hs.fireEvent(this, 'onAfterExpand');
+
+},
+
+
+prepareNextOutline : function() {
+ var key = this.key;
+ var outlineType = this.outlineType;
+ new hs.Outline(outlineType,
+ function () { try { hs.expanders[key].preloadNext(); } catch (e) {} });
+},
+
+
+preloadNext : function() {
+ var next = this.getAdjacentAnchor(1);
+ if (next && next.onclick.toString().match(/hs\.expand/))
+ var img = hs.createElement('img', { src: hs.getSrc(next) });
+},
+
+
+getAdjacentAnchor : function(op) {
+ var current = this.getAnchorIndex(), as = hs.anchors.groups[this.slideshowGroup || 'none'];
+
+ /*< ? if ($cfg->slideshow) : ?>s*/
+ if (!as[current + op] && this.slideshow && this.slideshow.repeat) {
+ if (op == 1) return as[0];
+ else if (op == -1) return as[as.length-1];
+ }
+ /*< ? endif ?>s*/
+ return as[current + op] || null;
+},
+
+getAnchorIndex : function() {
+ var arr = hs.getAnchors().groups[this.slideshowGroup || 'none'];
+ if (arr) for (var i = 0; i < arr.length; i++) {
+ if (arr[i] == this.a) return i;
+ }
+ return null;
+},
+
+
+getNumber : function() {
+ if (this[this.numberPosition]) {
+ var arr = hs.anchors.groups[this.slideshowGroup || 'none'];
+ if (arr) {
+ var s = hs.lang.number.replace('%1', this.getAnchorIndex() + 1).replace('%2', arr.length);
+ this[this.numberPosition].innerHTML =
+ '<div class="highslide-number">'+ s +'</div>'+ this[this.numberPosition].innerHTML;
+ }
+ }
+},
+initSlideshow : function() {
+ if (!this.last) {
+ for (var i = 0; i < hs.slideshows.length; i++) {
+ var ss = hs.slideshows[i], sg = ss.slideshowGroup;
+ if (typeof sg == 'undefined' || sg === null || sg === this.slideshowGroup)
+ this.slideshow = new hs.Slideshow(this.key, ss);
+ }
+ } else {
+ this.slideshow = this.last.slideshow;
+ }
+ var ss = this.slideshow;
+ if (!ss) return;
+ var key = ss.expKey = this.key;
+
+ ss.checkFirstAndLast();
+ ss.disable('full-expand');
+ if (ss.controls) {
+ this.createOverlay(hs.extend(ss.overlayOptions || {}, {
+ overlayId: ss.controls,
+ hsId: 'controls',
+ zIndex: 5
+ }));
+ }
+ if (ss.thumbstrip) ss.thumbstrip.add(this);
+ if (!this.last && this.autoplay) ss.play(true);
+ if (ss.autoplay) {
+ ss.autoplay = setTimeout(function() {
+ hs.next(key);
+ }, (ss.interval || 500));
+ }
+},
+
+cancelLoading : function() {
+ hs.discardElement (this.wrapper);
+ hs.expanders[this.key] = null;
+ if (hs.upcoming == this.a) hs.upcoming = null;
+ hs.undim(this.key);
+ if (this.loading) hs.loading.style.left = '-9999px';
+ hs.fireEvent(this, 'onHideLoading');
+},
+
+writeCredits : function () {
+ if (this.credits) return;
+ this.credits = hs.createElement('a', {
+ href: hs.creditsHref,
+ target: hs.creditsTarget,
+ className: 'highslide-credits',
+ innerHTML: hs.lang.creditsText,
+ title: hs.lang.creditsTitle
+ });
+ this.createOverlay({
+ overlayId: this.credits,
+ position: this.creditsPosition || 'top left',
+ hsId: 'credits'
+ });
+},
+
+getInline : function(types, addOverlay) {
+ for (var i = 0; i < types.length; i++) {
+ var type = types[i], s = null;
+ if (type == 'caption' && !hs.fireEvent(this, 'onBeforeGetCaption')) return;
+ else if (type == 'heading' && !hs.fireEvent(this, 'onBeforeGetHeading')) return;
+ if (!this[type +'Id'] && this.thumbsUserSetId)
+ this[type +'Id'] = type +'-for-'+ this.thumbsUserSetId;
+ if (this[type +'Id']) this[type] = hs.getNode(this[type +'Id']);
+ if (!this[type] && !this[type +'Text'] && this[type +'Eval']) try {
+ s = eval(this[type +'Eval']);
+ } catch (e) {}
+ if (!this[type] && this[type +'Text']) {
+ s = this[type +'Text'];
+ }
+ if (!this[type] && !s) {
+ this[type] = hs.getNode(this.a['_'+ type + 'Id']);
+ if (!this[type]) {
+ var next = this.a.nextSibling;
+ while (next && !hs.isHsAnchor(next)) {
+ if ((new RegExp('highslide-'+ type)).test(next.className || null)) {
+ if (!next.id) this.a['_'+ type + 'Id'] = next.id = 'hsId'+ hs.idCounter++;
+ this[type] = hs.getNode(next.id);
+ break;
+ }
+ next = next.nextSibling;
+ }
+ }
+ }
+ if (!this[type] && !s && this.numberPosition == type) s = '\n';
+
+ if (!this[type] && s) this[type] = hs.createElement('div',
+ { className: 'highslide-'+ type, innerHTML: s } );
+
+ if (addOverlay && this[type]) {
+ var o = { position: (type == 'heading') ? 'above' : 'below' };
+ for (var x in this[type+'Overlay']) o[x] = this[type+'Overlay'][x];
+ o.overlayId = this[type];
+ this.createOverlay(o);
+ }
+ }
+},
+
+
+// on end move and resize
+doShowHide : function(visibility) {
+ if (hs.hideSelects) this.showHideElements('SELECT', visibility);
+ if (hs.hideIframes) this.showHideElements('IFRAME', visibility);
+ if (hs.geckoMac) this.showHideElements('*', visibility);
+},
+showHideElements : function (tagName, visibility) {
+ var els = document.getElementsByTagName(tagName);
+ var prop = tagName == '*' ? 'overflow' : 'visibility';
+ for (var i = 0; i < els.length; i++) {
+ if (prop == 'visibility' || (document.defaultView.getComputedStyle(
+ els[i], "").getPropertyValue('overflow') == 'auto'
+ || els[i].getAttribute('hidden-by') != null)) {
+ var hiddenBy = els[i].getAttribute('hidden-by');
+ if (visibility == 'visible' && hiddenBy) {
+ hiddenBy = hiddenBy.replace('['+ this.key +']', '');
+ els[i].setAttribute('hidden-by', hiddenBy);
+ if (!hiddenBy) els[i].style[prop] = els[i].origProp;
+ } else if (visibility == 'hidden') { // hide if behind
+ var elPos = hs.getPosition(els[i]);
+ elPos.w = els[i].offsetWidth;
+ elPos.h = els[i].offsetHeight;
+ if (!this.dimmingOpacity) { // hide all if dimming
+
+ var clearsX = (elPos.x + elPos.w < this.x.get('opos')
+ || elPos.x > this.x.get('opos') + this.x.get('osize'));
+ var clearsY = (elPos.y + elPos.h < this.y.get('opos')
+ || elPos.y > this.y.get('opos') + this.y.get('osize'));
+ }
+ var wrapperKey = hs.getWrapperKey(els[i]);
+ if (!clearsX && !clearsY && wrapperKey != this.key) { // element falls behind image
+ if (!hiddenBy) {
+ els[i].setAttribute('hidden-by', '['+ this.key +']');
+ els[i].origProp = els[i].style[prop];
+ els[i].style[prop] = 'hidden';
+
+ } else if (hiddenBy.indexOf('['+ this.key +']') == -1) {
+ els[i].setAttribute('hidden-by', hiddenBy + '['+ this.key +']');
+ }
+ } else if ((hiddenBy == '['+ this.key +']' || hs.focusKey == wrapperKey)
+ && wrapperKey != this.key) { // on move
+ els[i].setAttribute('hidden-by', '');
+ els[i].style[prop] = els[i].origProp || '';
+ } else if (hiddenBy && hiddenBy.indexOf('['+ this.key +']') > -1) {
+ els[i].setAttribute('hidden-by', hiddenBy.replace('['+ this.key +']', ''));
+ }
+
+ }
+ }
+ }
+},
+
+focus : function() {
+ this.wrapper.style.zIndex = hs.zIndexCounter += 2;
+ // blur others
+ for (var i = 0; i < hs.expanders.length; i++) {
+ if (hs.expanders[i] && i == hs.focusKey) {
+ var blurExp = hs.expanders[i];
+ blurExp.content.className += ' highslide-'+ blurExp.contentType +'-blur';
+ if (blurExp.isImage) {
+ blurExp.content.style.cursor = hs.ie ? 'hand' : 'pointer';
+ blurExp.content.title = hs.lang.focusTitle;
+ }
+ hs.fireEvent(blurExp, 'onBlur');
+ }
+ }
+
+ // focus this
+ if (this.outline) this.outline.table.style.zIndex
+ = this.wrapper.style.zIndex - 1;
+ this.content.className = 'highslide-'+ this.contentType;
+ if (this.isImage) {
+ this.content.title = hs.lang.restoreTitle;
+
+ if (hs.restoreCursor) {
+ hs.styleRestoreCursor = window.opera ? 'pointer' : 'url('+ hs.graphicsDir + hs.restoreCursor +'), pointer';
+ if (hs.ie && hs.uaVersion < 6) hs.styleRestoreCursor = 'hand';
+ this.content.style.cursor = hs.styleRestoreCursor;
+ }
+ }
+ hs.focusKey = this.key;
+ hs.addEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
+ hs.fireEvent(this, 'onFocus');
+},
+moveTo: function(x, y) {
+ this.x.setPos(x);
+ this.y.setPos(y);
+},
+resize : function (e) {
+ var w, h, r = e.width / e.height;
+ w = Math.max(e.width + e.dX, Math.min(this.minWidth, this.x.full));
+ if (this.isImage && Math.abs(w - this.x.full) < 12) w = this.x.full;
+ h = this.isHtml ? e.height + e.dY : w / r;
+ if (h < Math.min(this.minHeight, this.y.full)) {
+ h = Math.min(this.minHeight, this.y.full);
+ if (this.isImage) w = h * r;
+ }
+ this.resizeTo(w, h);
+},
+resizeTo: function(w, h) {
+ this.y.setSize(h);
+ this.x.setSize(w);
+ this.wrapper.style.height = this.y.get('wsize') +'px';
+},
+
+close : function() {
+ if (this.isClosing || !this.isExpanded) return;
+ if (this.transitions[1] == 'crossfade' && hs.upcoming) {
+ hs.getExpander(hs.upcoming).cancelLoading();
+ hs.upcoming = null;
+ }
+ if (!hs.fireEvent(this, 'onBeforeClose')) return;
+ this.isClosing = true;
+ if (this.slideshow && !hs.upcoming) this.slideshow.pause();
+
+ hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
+
+ try {
+ if (this.isHtml) this.htmlPrepareClose();
+ this.content.style.cursor = 'default';
+ this.changeSize(
+ 0, {
+ wrapper: {
+ width : this.x.t,
+ height : this.y.t,
+ left: this.x.tpos - this.x.cb + this.x.tb,
+ top: this.y.tpos - this.y.cb + this.y.tb
+ },
+ content: {
+ left: 0,
+ top: 0,
+ width: this.x.t,
+ height: this.y.t
+ }
+ }, hs.restoreDuration
+ );
+ } catch (e) { this.afterClose(); }
+},
+
+htmlPrepareClose : function() {
+ if (hs.geckoMac) { // bad redraws
+ if (!hs.mask) hs.mask = hs.createElement('div', null,
+ { position: 'absolute' }, hs.container);
+ hs.setStyles(hs.mask, { width: this.x.size +'px', height: this.y.size +'px',
+ left: this.x.pos +'px', top: this.y.pos +'px', display: 'block' });
+ }
+ if (this.objectType == 'swf') try { hs.$(this.body.id).StopPlay(); } catch (e) {}
+
+ if (this.objectLoadTime == 'after' && !this.preserveContent) this.destroyObject();
+ if (this.scrollerDiv && this.scrollerDiv != this.scrollingContent)
+ this.scrollerDiv.style.overflow = 'hidden';
+},
+
+destroyObject : function () {
+ if (hs.ie && this.iframe)
+ try { this.iframe.contentWindow.document.body.innerHTML = ''; } catch (e) {}
+ if (this.objectType == 'swf') swfobject.removeSWF(this.body.id);
+ this.body.innerHTML = '';
+},
+
+sleep : function() {
+ if (this.outline) this.outline.table.style.display = 'none';
+ this.releaseMask = null;
+ this.wrapper.style.display = 'none';
+ hs.push(hs.sleeping, this);
+},
+
+awake : function() {try {
+
+ hs.expanders[this.key] = this;
+
+ if (!hs.allowMultipleInstances &&hs.focusKey != this.key) {
+ try { hs.expanders[hs.focusKey].close(); } catch (e){}
+ }
+
+ var z = hs.zIndexCounter++, stl = { display: '', zIndex: z };
+ hs.setStyles (this.wrapper, stl);
+ this.isClosing = false;
+
+ var o = this.outline || 0;
+ if (o) {
+ if (!this.outlineWhileAnimating) stl.visibility = 'hidden';
+ hs.setStyles (o.table, stl);
+ }
+ if (this.slideshow) {
+ this.initSlideshow();
+ }
+
+ this.show();
+} catch (e) {}
+
+
+},
+
+createOverlay : function (o) {
+ var el = o.overlayId,
+ relToVP = (o.relativeTo == 'viewport' && !/panel$/.test(o.position));
+ if (typeof el == 'string') el = hs.getNode(el);
+ if (o.html) el = hs.createElement('div', { innerHTML: o.html });
+ if (!el || typeof el == 'string') return;
+ if (!hs.fireEvent(this, 'onCreateOverlay', { overlay: el })) return;
+ el.style.display = 'block';
+ o.hsId = o.hsId || o.overlayId;
+ if (this.transitions[1] == 'crossfade' && this.reuseOverlay(o, el)) return;
+ this.genOverlayBox();
+ var width = o.width && /^[0-9]+(px|%)$/.test(o.width) ? o.width : 'auto';
+ if (/^(left|right)panel$/.test(o.position) && !/^[0-9]+px$/.test(o.width)) width = '200px';
+ var overlay = hs.createElement(
+ 'div', {
+ id: 'hsId'+ hs.idCounter++,
+ hsId: o.hsId
+ }, {
+ position: 'absolute',
+ visibility: 'hidden',
+ width: width,
+ direction: hs.lang.cssDirection || '',
+ opacity: 0
+ },
+ relToVP ? hs.viewport :this.overlayBox,
+ true
+ );
+ if (relToVP) overlay.hsKey = this.key;
+
+ overlay.appendChild(el);
+ hs.extend(overlay, {
+ opacity: 1,
+ offsetX: 0,
+ offsetY: 0,
+ dur: (o.fade === 0 || o.fade === false || (o.fade == 2 && hs.ie)) ? 0 : 250
+ });
+ hs.extend(overlay, o);
+
+
+ if (this.gotOverlays) {
+ this.positionOverlay(overlay);
+ if (!overlay.hideOnMouseOut || this.mouseIsOver)
+ hs.animate(overlay, { opacity: overlay.opacity }, overlay.dur);
+ }
+ hs.push(this.overlays, hs.idCounter - 1);
+},
+positionOverlay : function(overlay) {
+ var p = overlay.position || 'middle center',
+ relToVP = (overlay.relativeTo == 'viewport'),
+ offX = overlay.offsetX,
+ offY = overlay.offsetY;
+ if (relToVP) {
+ hs.viewport.style.display = 'block';
+ overlay.hsKey = this.key;
+ if (overlay.offsetWidth > overlay.parentNode.offsetWidth)
+ overlay.style.width = '100%';
+ } else
+ if (overlay.parentNode != this.overlayBox) this.overlayBox.appendChild(overlay);
+ if (/left$/.test(p)) overlay.style.left = offX +'px';
+
+ if (/center$/.test(p)) hs.setStyles (overlay, {
+ left: '50%',
+ marginLeft: (offX - Math.round(overlay.offsetWidth / 2)) +'px'
+ });
+
+ if (/right$/.test(p)) overlay.style.right = - offX +'px';
+
+ if (/^leftpanel$/.test(p)) {
+ hs.setStyles(overlay, {
+ right: '100%',
+ marginRight: this.x.cb +'px',
+ top: - this.y.cb +'px',
+ bottom: - this.y.cb +'px',
+ overflow: 'auto'
+ });
+ this.x.p1 = overlay.offsetWidth;
+
+ } else if (/^rightpanel$/.test(p)) {
+ hs.setStyles(overlay, {
+ left: '100%',
+ marginLeft: this.x.cb +'px',
+ top: - this.y.cb +'px',
+ bottom: - this.y.cb +'px',
+ overflow: 'auto'
+ });
+ this.x.p2 = overlay.offsetWidth;
+ }
+ var parOff = overlay.parentNode.offsetHeight;
+ overlay.style.height = 'auto';
+ if (relToVP && overlay.offsetHeight > parOff)
+ overlay.style.height = hs.ieLt7 ? parOff +'px' : '100%';
+
+ if (/^top/.test(p)) overlay.style.top = offY +'px';
+ if (/^middle/.test(p)) hs.setStyles (overlay, {
+ top: '50%',
+ marginTop: (offY - Math.round(overlay.offsetHeight / 2)) +'px'
+ });
+ if (/^bottom/.test(p)) overlay.style.bottom = - offY +'px';
+ if (/^above$/.test(p)) {
+ hs.setStyles(overlay, {
+ left: (- this.x.p1 - this.x.cb) +'px',
+ right: (- this.x.p2 - this.x.cb) +'px',
+ bottom: '100%',
+ marginBottom: this.y.cb +'px',
+ width: 'auto'
+ });
+ this.y.p1 = overlay.offsetHeight;
+
+ } else if (/^below$/.test(p)) {
+ hs.setStyles(overlay, {
+ position: 'relative',
+ left: (- this.x.p1 - this.x.cb) +'px',
+ right: (- this.x.p2 - this.x.cb) +'px',
+ top: '100%',
+ marginTop: this.y.cb +'px',
+ width: 'auto'
+ });
+ this.y.p2 = overlay.offsetHeight;
+ overlay.style.position = 'absolute';
+ }
+},
+
+getOverlays : function() {
+ this.getInline(['heading', 'caption'], true);
+ this.getNumber();
+ if (this.caption) hs.fireEvent(this, 'onAfterGetCaption');
+ if (this.heading) hs.fireEvent(this, 'onAfterGetHeading');
+ if (this.heading && this.dragByHeading) this.heading.className += ' highslide-move';
+ if (hs.showCredits) this.writeCredits();
+ for (var i = 0; i < hs.overlays.length; i++) {
+ var o = hs.overlays[i], tId = o.thumbnailId, sg = o.slideshowGroup;
+ if ((!tId && !sg) || (tId && tId == this.thumbsUserSetId)
+ || (sg && sg === this.slideshowGroup)) {
+ if (this.isImage || (this.isHtml && o.useOnHtml))
+ this.createOverlay(o);
+ }
+ }
+ var os = [];
+ for (var i = 0; i < this.overlays.length; i++) {
+ var o = hs.$('hsId'+ this.overlays[i]);
+ if (/panel$/.test(o.position)) this.positionOverlay(o);
+ else hs.push(os, o);
+ }
+ for (var i = 0; i < os.length; i++) this.positionOverlay(os[i]);
+ this.gotOverlays = true;
+},
+genOverlayBox : function() {
+ if (!this.overlayBox) this.overlayBox = hs.createElement (
+ 'div', {
+ className: this.wrapperClassName
+ }, {
+ position : 'absolute',
+ width: (this.x.size || (this.useBox ? this.width : null)
+ || this.x.full) +'px',
+ height: (this.y.size || this.y.full) +'px',
+ visibility : 'hidden',
+ overflow : 'hidden',
+ zIndex : hs.ie ? 4 : 'auto'
+ },
+ hs.container,
+ true
+ );
+},
+sizeOverlayBox : function(doWrapper, doPanels) {
+ var overlayBox = this.overlayBox,
+ x = this.x,
+ y = this.y;
+ hs.setStyles( overlayBox, {
+ width: x.size +'px',
+ height: y.size +'px'
+ });
+ if (doWrapper || doPanels) {
+ for (var i = 0; i < this.overlays.length; i++) {
+ var o = hs.$('hsId'+ this.overlays[i]);
+ var ie6 = (hs.ieLt7 || document.compatMode == 'BackCompat');
+ if (o && /^(above|below)$/.test(o.position)) {
+ if (ie6) {
+ o.style.width = (overlayBox.offsetWidth + 2 * x.cb
+ + x.p1 + x.p2) +'px';
+ }
+ y[o.position == 'above' ? 'p1' : 'p2'] = o.offsetHeight;
+ }
+ if (o && ie6 && /^(left|right)panel$/.test(o.position)) {
+ o.style.height = (overlayBox.offsetHeight + 2* y.cb) +'px';
+ }
+ }
+ }
+ if (doWrapper) {
+ hs.setStyles(this.content, {
+ top: y.p1 +'px'
+ });
+ hs.setStyles(overlayBox, {
+ top: (y.p1 + y.cb) +'px'
+ });
+ }
+},
+
+showOverlays : function() {
+ var b = this.overlayBox;
+ b.className = '';
+ hs.setStyles(b, {
+ top: (this.y.p1 + this.y.cb) +'px',
+ left: (this.x.p1 + this.x.cb) +'px',
+ overflow : 'visible'
+ });
+ if (hs.safari) b.style.visibility = 'visible';
+ this.wrapper.appendChild (b);
+ for (var i = 0; i < this.overlays.length; i++) {
+ var o = hs.$('hsId'+ this.overlays[i]);
+ o.style.zIndex = o.zIndex || 4;
+ if (!o.hideOnMouseOut || this.mouseIsOver) {
+ o.style.visibility = 'visible';
+ hs.setStyles(o, { visibility: 'visible', display: '' });
+ hs.animate(o, { opacity: o.opacity }, o.dur);
+ }
+ }
+},
+
+destroyOverlays : function() {
+ if (!this.overlays.length) return;
+ if (this.slideshow) {
+ var c = this.slideshow.controls;
+ if (c && hs.getExpander(c) == this) c.parentNode.removeChild(c);
+ }
+ for (var i = 0; i < this.overlays.length; i++) {
+ var o = hs.$('hsId'+ this.overlays[i]);
+ if (o && o.parentNode == hs.viewport && hs.getExpander(o) == this) hs.discardElement(o);
+ }
+ if (this.isHtml && this.preserveContent) {
+ this.overlayBox.style.top = '-9999px';
+ hs.container.appendChild(this.overlayBox);
+ } else
+ hs.discardElement(this.overlayBox);
+},
+
+
+
+createFullExpand : function () {
+ if (this.slideshow && this.slideshow.controls) {
+ this.slideshow.enable('full-expand');
+ return;
+ }
+ this.fullExpandLabel = hs.createElement(
+ 'a', {
+ href: 'javascript:hs.expanders['+ this.key +'].doFullExpand();',
+ title: hs.lang.fullExpandTitle,
+ className: 'highslide-full-expand'
+ }
+ );
+ if (!hs.fireEvent(this, 'onCreateFullExpand')) return;
+
+ this.createOverlay({
+ overlayId: this.fullExpandLabel,
+ position: hs.fullExpandPosition,
+ hideOnMouseOut: true,
+ opacity: hs.fullExpandOpacity
+ });
+},
+
+doFullExpand : function () {
+ try {
+ if (!hs.fireEvent(this, 'onDoFullExpand')) return;
+ if (this.fullExpandLabel) hs.discardElement(this.fullExpandLabel);
+
+ this.focus();
+ var xSize = this.x.size;
+ this.resizeTo(this.x.full, this.y.full);
+
+ var xpos = this.x.pos - (this.x.size - xSize) / 2;
+ if (xpos < hs.marginLeft) xpos = hs.marginLeft;
+
+ this.moveTo(xpos, this.y.pos);
+ this.doShowHide('hidden');
+
+ } catch (e) {
+ this.error(e);
+ }
+},
+
+
+afterClose : function () {
+ this.a.className = this.a.className.replace('highslide-active-anchor', '');
+
+ this.doShowHide('visible');
+
+ if (this.isHtml && this.preserveContent
+ && this.transitions[1] != 'crossfade') {
+ this.sleep();
+ } else {
+ if (this.outline && this.outlineWhileAnimating) this.outline.destroy();
+
+ hs.discardElement(this.wrapper);
+ }
+ if (hs.mask) hs.mask.style.display = 'none';
+ this.destroyOverlays();
+ if (!hs.viewport.childNodes.length) hs.viewport.style.display = 'none';
+
+ if (this.dimmingOpacity) hs.undim(this.key);
+ hs.fireEvent(this, 'onAfterClose');
+ hs.expanders[this.key] = null;
+ hs.reOrder();
+}
+
+};
+
+
+// hs.Ajax object prototype
+hs.Ajax = function (a, content, pre) {
+ this.a = a;
+ this.content = content;
+ this.pre = pre;
+};
+
+hs.Ajax.prototype = {
+run : function () {
+ var xhr;
+ if (!this.src) this.src = hs.getSrc(this.a);
+ if (this.src.match('#')) {
+ var arr = this.src.split('#');
+ this.src = arr[0];
+ this.id = arr[1];
+ }
+ if (hs.cachedGets[this.src]) {
+ this.cachedGet = hs.cachedGets[this.src];
+ if (this.id) this.getElementContent();
+ else this.loadHTML();
+ return;
+ }
+ try { xhr = new XMLHttpRequest(); }
+ catch (e) {
+ try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); }
+ catch (e) {
+ try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
+ catch (e) { this.onError(); }
+ }
+ }
+ var pThis = this;
+ xhr.onreadystatechange = function() {
+ if(pThis.xhr.readyState == 4) {
+ if (pThis.id) pThis.getElementContent();
+ else pThis.loadHTML();
+ }
+ };
+ var src = this.src;
+ this.xhr = xhr;
+ if (hs.forceAjaxReload)
+ src = src.replace(/$/, (/\?/.test(src) ? '&' : '?') +'dummy='+ (new Date()).getTime());
+ xhr.open('GET', src, true);
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ xhr.send(null);
+},
+
+getElementContent : function() {
+ hs.init();
+ var attribs = window.opera || hs.ie6SSL ? { src: 'about:blank' } : null;
+
+ this.iframe = hs.createElement('iframe', attribs,
+ { position: 'absolute', top: '-9999px' }, hs.container);
+
+ this.loadHTML();
+},
+
+loadHTML : function() {
+ var s = this.cachedGet || this.xhr.responseText,
+ regBody;
+ if (this.pre) hs.cachedGets[this.src] = s;
+ if (!hs.ie || hs.uaVersion >= 5.5) {
+ s = s.replace(new RegExp('<link[^>]*>', 'gi'), '')
+ .replace(new RegExp('<script[^>]*>.*?</script>', 'gi'), '');
+ if (this.iframe) {
+ var doc = this.iframe.contentDocument;
+ if (!doc && this.iframe.contentWindow) doc = this.iframe.contentWindow.document;
+ if (!doc) { // Opera
+ var pThis = this;
+ setTimeout(function() { pThis.loadHTML(); }, 25);
+ return;
+ }
+ doc.open();
+ doc.write(s);
+ doc.close();
+ try { s = doc.getElementById(this.id).innerHTML; } catch (e) {
+ try { s = this.iframe.document.getElementById(this.id).innerHTML; } catch (e) {} // opera
+ }
+ hs.discardElement(this.iframe);
+ } else {
+ regBody = /(<body[^>]*>|<\/body>)/ig;
+ if (regBody.test(s)) s = s.split(regBody)[hs.ie ? 1 : 2];
+
+ }
+ }
+ hs.getElementByClass(this.content, 'DIV', 'highslide-body').innerHTML = s;
+ this.onLoad();
+ for (var x in this) this[x] = null;
+}
+};
+
+
+hs.Slideshow = function (expKey, options) {
+ if (hs.dynamicallyUpdateAnchors !== false) hs.updateAnchors();
+ this.expKey = expKey;
+ for (var x in options) this[x] = options[x];
+ if (this.useControls) this.getControls();
+ if (this.thumbstrip) this.thumbstrip = hs.Thumbstrip(this);
+};
+hs.Slideshow.prototype = {
+getControls: function() {
+ this.controls = hs.createElement('div', { innerHTML: hs.replaceLang(hs.skin.controls) },
+ null, hs.container);
+
+ var buttons = ['play', 'pause', 'previous', 'next', 'move', 'full-expand', 'close'];
+ this.btn = {};
+ var pThis = this;
+ for (var i = 0; i < buttons.length; i++) {
+ this.btn[buttons[i]] = hs.getElementByClass(this.controls, 'li', 'highslide-'+ buttons[i]);
+ this.enable(buttons[i]);
+ }
+ this.btn.pause.style.display = 'none';
+ //this.disable('full-expand');
+},
+checkFirstAndLast: function() {
+ if (this.repeat || !this.controls) return;
+ var exp = hs.expanders[this.expKey],
+ cur = exp.getAnchorIndex(),
+ re = /disabled$/;
+ if (cur == 0)
+ this.disable('previous');
+ else if (re.test(this.btn.previous.getElementsByTagName('a')[0].className))
+ this.enable('previous');
+ if (cur + 1 == hs.anchors.groups[exp.slideshowGroup || 'none'].length) {
+ this.disable('next');
+ this.disable('play');
+ } else if (re.test(this.btn.next.getElementsByTagName('a')[0].className)) {
+ this.enable('next');
+ this.enable('play');
+ }
+},
+enable: function(btn) {
+ if (!this.btn) return;
+ var sls = this, a = this.btn[btn].getElementsByTagName('a')[0], re = /disabled$/;
+ a.onclick = function() {
+ sls[btn]();
+ return false;
+ };
+ if (re.test(a.className)) a.className = a.className.replace(re, '');
+},
+disable: function(btn) {
+ if (!this.btn) return;
+ var a = this.btn[btn].getElementsByTagName('a')[0];
+ a.onclick = function() { return false; };
+ if (!/disabled$/.test(a.className)) a.className += ' disabled';
+},
+hitSpace: function() {
+ if (this.autoplay) this.pause();
+ else this.play();
+},
+play: function(wait) {
+ if (this.btn) {
+ this.btn.play.style.display = 'none';
+ this.btn.pause.style.display = '';
+ }
+
+ this.autoplay = true;
+ if (!wait) hs.next(this.expKey);
+},
+pause: function() {
+ if (this.btn) {
+ this.btn.pause.style.display = 'none';
+ this.btn.play.style.display = '';
+ }
+
+ clearTimeout(this.autoplay);
+ this.autoplay = null;
+},
+previous: function() {
+ this.pause();
+ hs.previous(this.btn.previous);
+},
+next: function() {
+ this.pause();
+ hs.next(this.btn.next);
+},
+move: function() {},
+'full-expand': function() {
+ hs.getExpander().doFullExpand();
+},
+close: function() {
+ hs.close(this.btn.close);
+}
+};
+hs.Thumbstrip = function(slideshow) {
+ function add (exp) {
+ hs.extend(options || {}, {
+ overlayId: dom,
+ hsId: 'thumbstrip',
+ className: 'highslide-thumbstrip-'+ mode +'-overlay ' + (options.className || '')
+ });
+ if (hs.ieLt7) options.fade = 0;
+ exp.createOverlay(options);
+ hs.setStyles(dom.parentNode, { overflow: 'hidden' });
+ };
+
+ function scroll (delta) {
+ selectThumb(undefined, Math.round(delta * dom[isX ? 'offsetWidth' : 'offsetHeight'] * 0.7));
+ };
+
+ function selectThumb (i, scrollBy) {
+ if (i === undefined) for (var j = 0; j < group.length; j++) {
+ if (group[j] == hs.expanders[slideshow.expKey].a) {
+ i = j;
+ break;
+ }
+ }
+ if (i === undefined) return;
+ var as = dom.getElementsByTagName('a'),
+ active = as[i],
+ cell = active.parentNode,
+ left = isX ? 'Left' : 'Top',
+ right = isX ? 'Right' : 'Bottom',
+ width = isX ? 'Width' : 'Height',
+ offsetLeft = 'offset' + left,
+ offsetWidth = 'offset' + width,
+ overlayWidth = div.parentNode.parentNode[offsetWidth],
+ minTblPos = overlayWidth - table[offsetWidth],
+ curTblPos = parseInt(table.style[isX ? 'left' : 'top']) || 0,
+ tblPos = curTblPos,
+ mgnRight = 20;
+ if (scrollBy !== undefined) {
+ tblPos = curTblPos - scrollBy;
+
+ if (minTblPos > 0) minTblPos = 0;
+ if (tblPos > 0) tblPos = 0;
+ if (tblPos < minTblPos) tblPos = minTblPos;
+
+
+ } else {
+ for (var j = 0; j < as.length; j++) as[j].className = '';
+ active.className = 'highslide-active-anchor';
+ var activeLeft = i > 0 ? as[i - 1].parentNode[offsetLeft] : cell[offsetLeft],
+ activeRight = cell[offsetLeft] + cell[offsetWidth] +
+ (as[i + 1] ? as[i + 1].parentNode[offsetWidth] : 0);
+ if (activeRight > overlayWidth - curTblPos) tblPos = overlayWidth - activeRight;
+ else if (activeLeft < -curTblPos) tblPos = -activeLeft;
+ }
+ var markerPos = cell[offsetLeft] + (cell[offsetWidth] - marker[offsetWidth]) / 2 + tblPos;
+ hs.animate(table, isX ? { left: tblPos } : { top: tblPos }, null, 'easeOutQuad');
+ hs.animate(marker, isX ? { left: markerPos } : { top: markerPos }, null, 'easeOutQuad');
+ scrollUp.style.display = tblPos < 0 ? 'block' : 'none';
+ scrollDown.style.display = (tblPos > minTblPos) ? 'block' : 'none';
+
+ };
+
+
+ // initialize
+ var group = hs.anchors.groups[hs.expanders[slideshow.expKey].slideshowGroup || 'none'],
+ options = slideshow.thumbstrip,
+ mode = options.mode || 'horizontal',
+ floatMode = (mode == 'float'),
+ tree = floatMode ? ['div', 'ul', 'li', 'span'] : ['table', 'tbody', 'tr', 'td'],
+ isX = (mode == 'horizontal'),
+ dom = hs.createElement('div', {
+ className: 'highslide-thumbstrip highslide-thumbstrip-'+ mode,
+ innerHTML:
+ '<div class="highslide-thumbstrip-inner">'+
+ '<'+ tree[0] +'><'+ tree[1] +'></'+ tree[1] +'></'+ tree[0] +'></div>'+
+ '<div class="highslide-scroll-up"><div></div></div>'+
+ '<div class="highslide-scroll-down"><div></div></div>'+
+ '<div class="highslide-marker"><div></div></div>'
+ }, {
+ display: 'none'
+ }, hs.container),
+ domCh = dom.childNodes,
+ div = domCh[0],
+ scrollUp = domCh[1],
+ scrollDown = domCh[2],
+ marker = domCh[3],
+ table = div.firstChild,
+ tbody = dom.getElementsByTagName(tree[1])[0],
+ tr;
+ for (var i = 0; i < group.length; i++) {
+ if (i == 0 || !isX) tr = hs.createElement(tree[2], null, null, tbody);
+ (function(){
+ var a = group[i],
+ cell = hs.createElement(tree[3], null, null, tr),
+ pI = i;
+ hs.createElement('a', {
+ href: a.href,
+ onclick: function() {
+ hs.getExpander(this).focus();
+ return hs.transit(a);
+ },
+ innerHTML: hs.stripItemFormatter ? hs.stripItemFormatter(a) : a.innerHTML
+ }, null, cell);
+ })();
+ }
+ if (!floatMode) {
+ scrollUp.onclick = function () { scroll(-1); };
+ scrollDown.onclick = function() { scroll(1); };
+ hs.addEventListener(tbody, document.onmousewheel !== undefined ?
+ 'mousewheel' : 'DOMMouseScroll', function(e) {
+ var delta = 0;
+ e = e || window.event;
+ if (e.wheelDelta) {
+ delta = e.wheelDelta/120;
+ if (hs.opera) delta = -delta;
+ } else if (e.detail) {
+ delta = -e.detail/3;
+ }
+ if (delta) scroll(-delta * 0.2);
+ if (e.preventDefault) e.preventDefault();
+ e.returnValue = false;
+ });
+ }
+
+ return {
+ add: add,
+ selectThumb: selectThumb
+ }
+};
+hs.langDefaults = hs.lang;
+// history
+var HsExpander = hs.Expander;
+if (hs.ie && window == window.top) {
+ (function () {
+ try {
+ document.documentElement.doScroll('left');
+ } catch (e) {
+ setTimeout(arguments.callee, 50);
+ return;
+ }
+ hs.ready();
+ })();
+}
+hs.addEventListener(document, 'DOMContentLoaded', hs.ready);
+hs.addEventListener(window, 'load', hs.ready);
+
+// set handlers
+hs.addEventListener(document, 'ready', function() {
+ if (hs.expandCursor || hs.dimmingOpacity) {
+ var style = hs.createElement('style', { type: 'text/css' }, null,
+ document.getElementsByTagName('HEAD')[0]);
+
+ function addRule(sel, dec) {
+ if (!hs.ie) {
+ style.appendChild(document.createTextNode(sel + " {" + dec + "}"));
+ } else {
+ var last = document.styleSheets[document.styleSheets.length - 1];
+ if (typeof(last.addRule) == "object") last.addRule(sel, dec);
+ }
+ }
+ function fix(prop) {
+ return 'expression( ( ( ignoreMe = document.documentElement.'+ prop +
+ ' ? document.documentElement.'+ prop +' : document.body.'+ prop +' ) ) + \'px\' );';
+ }
+ if (hs.expandCursor) addRule ('.highslide img',
+ 'cursor: url('+ hs.graphicsDir + hs.expandCursor +'), pointer !important;');
+ addRule ('.highslide-viewport-size',
+ hs.ie && (hs.uaVersion < 7 || document.compatMode == 'BackCompat') ?
+ 'position: absolute; '+
+ 'left:'+ fix('scrollLeft') +
+ 'top:'+ fix('scrollTop') +
+ 'width:'+ fix('clientWidth') +
+ 'height:'+ fix('clientHeight') :
+ 'position: fixed; width: 100%; height: 100%; left: 0; top: 0');
+ }
+});
+hs.addEventListener(window, 'resize', function() {
+ hs.getPageSize();
+ if (hs.viewport) for (var i = 0; i < hs.viewport.childNodes.length; i++) {
+ var node = hs.viewport.childNodes[i],
+ exp = hs.getExpander(node);
+ exp.positionOverlay(node);
+ if (node.hsId == 'thumbstrip') exp.slideshow.thumbstrip.selectThumb();
+ }
+});
+hs.addEventListener(document, 'mousemove', function(e) {
+ hs.mouse = { x: e.clientX, y: e.clientY };
+});
+hs.addEventListener(document, 'mousedown', hs.mouseClickHandler);
+hs.addEventListener(document, 'mouseup', hs.mouseClickHandler);
+hs.addEventListener(document, 'ready', hs.setClickEvents);
+hs.addEventListener(window, 'load', hs.preloadImages);
+hs.addEventListener(window, 'load', hs.preloadAjax);
+} \ No newline at end of file
diff --git a/tools/infra-dashboard/js/highslide.config.min.js b/tools/infra-dashboard/js/highslide.config.min.js
new file mode 100644
index 00000000..4d1a9b3d
--- /dev/null
+++ b/tools/infra-dashboard/js/highslide.config.min.js
@@ -0,0 +1 @@
+hs.outlineType="rounded-white",hs.wrapperClassName="draggable-header",hs.captionEval="this.a.title",hs.showCredits=!1,hs.marginTop=20,hs.marginRight=20,hs.marginBottom=20,hs.marginLeft=20; \ No newline at end of file
diff --git a/tools/infra-dashboard/js/jquery-1.7.2.js b/tools/infra-dashboard/js/jquery-1.7.2.js
new file mode 100644
index 00000000..3774ff98
--- /dev/null
+++ b/tools/infra-dashboard/js/jquery-1.7.2.js
@@ -0,0 +1,9404 @@
+/*!
+ * jQuery JavaScript Library v1.7.2
+ * 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: Wed Mar 21 12:46:34 2012 -0700
+ */
+(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.2",
+
+ // 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";
+ },
+
+ isWindow: function( obj ) {
+ return obj != null && obj == obj.window;
+ },
+
+ 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 ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+ 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, fn, key, value, chainable, emptyGet, pass ) {
+ var exec,
+ bulk = key == null,
+ i = 0,
+ length = elems.length;
+
+ // Sets many values
+ if ( key && typeof key === "object" ) {
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
+ }
+ chainable = 1;
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = pass === undefined && jQuery.isFunction( value );
+
+ if ( bulk ) {
+ // Bulk operations only iterate when executing function values
+ if ( exec ) {
+ exec = fn;
+ fn = function( elem, key, value ) {
+ return exec.call( jQuery( elem ), value );
+ };
+
+ // Otherwise they run against the entire set
+ } else {
+ fn.call( elems, value );
+ fn = null;
+ }
+ }
+
+ if ( fn ) {
+ for (; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+ }
+
+ chainable = 1;
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ 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 was already fired
+ fired,
+ // 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 ];
+ fired = true;
+ 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 !!fired;
+ }
+ };
+
+ 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,
+ 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,
+ pixelMargin: true
+ };
+
+ // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead
+ jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat");
+
+ // 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");
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "name", "t" );
+
+ 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 );
+
+ // 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 = div = input = null;
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, outer, inner, table, td, offsetSupport,
+ marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight,
+ paddingMarginBorderVisibility, paddingMarginBorder,
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ conMarginTop = 1;
+ paddingMarginBorder = "padding:0;margin:0;border:";
+ positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;";
+ paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;";
+ style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;";
+ html = "<div " + style + "display:block;'><div style='" + paddingMarginBorder + "0;display:block;overflow:hidden;'></div></div>" +
+ "<table " + style + "' cellpadding='0' cellspacing='0'>" +
+ "<tr><td></td></tr></table>";
+
+ container = document.createElement("div");
+ container.style.cssText = paddingMarginBorderVisibility + "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='" + paddingMarginBorder + "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 );
+
+ // 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 ) {
+ div.innerHTML = "";
+ 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;
+ }
+
+ 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.innerHTML = "";
+ div.style.width = div.style.padding = "1px";
+ div.style.border = 0;
+ div.style.overflow = "hidden";
+ div.style.display = "inline";
+ div.style.zoom = 1;
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "block";
+ div.style.overflow = "visible";
+ div.innerHTML = "<div style='width:5px;'></div>";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+ }
+
+ div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility;
+ 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 );
+
+ if ( window.getComputedStyle ) {
+ div.style.marginTop = "1%";
+ support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%";
+ }
+
+ if ( typeof container.style.zoom !== "undefined" ) {
+ container.style.zoom = 1;
+ }
+
+ body.removeChild( container );
+ marginDiv = 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, part, attr, name, l,
+ elem = this[0],
+ i = 0,
+ data = null;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
+
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ attr = elem.attributes;
+ for ( l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ jQuery._data( elem, "parsedAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split( ".", 2 );
+ parts[1] = parts[1] ? "." + parts[1] : "";
+ part = parts[1] + "!";
+
+ return jQuery.access( this, function( value ) {
+
+ if ( value === undefined ) {
+ data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && elem ) {
+ data = jQuery.data( elem, key );
+ data = dataAttr( elem, key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ }
+
+ parts[1] = value;
+ this.each(function() {
+ var self = jQuery( this );
+
+ self.triggerHandler( "setData" + part, parts );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + part, parts );
+ });
+ }, null, value, arguments.length > 1, null, false );
+ },
+
+ 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 ) ? +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 ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ 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( object );
+ }
+});
+
+
+
+
+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, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var 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.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var 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.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // 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, isBool,
+ 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;
+ isBool = rboolean.test( name );
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ // Do not do this for boolean attributes (see #10870)
+ if ( !isBool ) {
+ jQuery.attr( elem, name, "" );
+ }
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( isBool && 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,
+ coords: 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 = /(?:^|\s)hover(\.\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;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ 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: selector && 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,
+ special = jQuery.event.special[ event.type ] || {},
+ 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;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !(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 ) {
+
+ // Don't process events on disabled elements (#6911, #8165)
+ if ( cur.disabled !== true ) {
+ 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();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ 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 ) {
+ event._submit_bubble = true;
+ });
+ form._submit_attached = true;
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ postDispatch: function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( event._submit_bubble ) {
+ delete event._submit_bubble;
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ }
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ 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" ) { // && selector != null
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on( types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ var handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( 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 || nodeType === 11 ) {
+ // 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;
+
+ /* falls through */
+ 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) );
+}
+// Expose origPOS
+// "global" as in regardless of relation to brackets/parens
+Expr.match.globalPOS = origPOS;
+
+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.globalPOS,
+ // 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|bdi|canvas|data|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 + ")[\\s/>]", "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( value ) {
+ return jQuery.access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
+ }, null, value, arguments.length );
+ },
+
+ 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 ) {
+ return jQuery.access( this, function( value ) {
+ var elem = this[0] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined ) {
+ return elem.nodeType === 1 ?
+ elem.innerHTML.replace( rinlinejQuery, "" ) :
+ null;
+ }
+
+
+ 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 (; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ elem = this[i] || {};
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName( "*" ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function( 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, function( i, elem ) {
+ if ( elem.src ) {
+ jQuery.ajax({
+ type: "GET",
+ global: false,
+ 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 );
+ }
+ });
+ }
+ }
+
+ 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 ] );
+ }
+ }
+ }
+
+ // 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;
+
+ // IE blanks contents when cloning scripts
+ } else if ( nodeName === "script" && dest.text !== src.text ) {
+ dest.text = src.text;
+ }
+
+ // Event data gets referenced instead of copied if the expando
+ // gets copied too
+ dest.removeAttribute( jQuery.expando );
+
+ // Clear flags for bubbling special change/submit events, they must
+ // be reattached when the newly cloned events are first activated
+ dest.removeAttribute( "_submit_attached" );
+ dest.removeAttribute( "_change_attached" );
+}
+
+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 || jQuery.isXMLDoc(elem) || !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, script, j,
+ ret = [];
+
+ 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;
+ }
+
+ 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"),
+ safeChildNodes = safeFragment.childNodes,
+ remove;
+
+ // 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;
+
+ // Clear elements from DocumentFragment (safeFragment or otherwise)
+ // to avoid hoarding elements. Fixes #11356
+ if ( div ) {
+ div.parentNode.removeChild( div );
+
+ // Guard against -1 index exceptions in FF3.6
+ if ( safeChildNodes.length > 0 ) {
+ remove = safeChildNodes[ safeChildNodes.length - 1 ];
+
+ if ( remove && remove.parentNode ) {
+ remove.parentNode.removeChild( remove );
+ }
+ }
+ }
+ }
+ }
+
+ // 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++ ) {
+ script = ret[i];
+ if ( scripts && jQuery.nodeName( script, "script" ) && (!script.type || rscriptType.test( script.type )) ) {
+ scripts.push( script.parentNode ? script.parentNode.removeChild( script ) : script );
+
+ } else {
+ if ( script.nodeType === 1 ) {
+ var jsTags = jQuery.grep( script.getElementsByTagName( "script" ), checkScriptType );
+
+ ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+ }
+ fragment.appendChild( script );
+ }
+ }
+ }
+
+ 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 ];
+ }
+ }
+ }
+});
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+ ropacity = /opacity=([^)]*)/,
+ // fixed for IE9, see #8346
+ rupper = /([A-Z]|^ms)/g,
+ rnum = /^[\-+]?(?:\d*\.)?\d+$/i,
+ rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,
+ rrelNum = /^([\-+])=([\-+.\de]+)/,
+ rmargin = /^margin/,
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+
+ // order is important!
+ cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+
+ curCSS,
+
+ getComputedStyle,
+ currentStyle;
+
+jQuery.fn.css = function( name, value ) {
+ return jQuery.access( this, function( elem, name, value ) {
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+};
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+
+ } 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 = {},
+ ret, name;
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+ }
+});
+
+// DEPRECATED in 1.3, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+ getComputedStyle = function( elem, name ) {
+ var ret, defaultView, computedStyle, width,
+ style = elem.style;
+
+ 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 );
+ }
+ }
+
+ // A tribute to the "awesome hack by Dean Edwards"
+ // WebKit uses "computed value (percentage if specified)" instead of "used value" for margins
+ // which is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+ if ( !jQuery.support.pixelMargin && computedStyle && rmargin.test( name ) && rnumnonpx.test( ret ) ) {
+ width = style.width;
+ style.width = ret;
+ ret = computedStyle.width;
+ style.width = width;
+ }
+
+ 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 ( rnumnonpx.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;
+ 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 getWidthOrHeight( elem, name, extra ) {
+
+ // Start with offset property
+ var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ i = name === "width" ? 1 : 0,
+ len = 4;
+
+ if ( val > 0 ) {
+ if ( extra !== "border" ) {
+ for ( ; i < len; i += 2 ) {
+ if ( !extra ) {
+ val -= parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
+ }
+ if ( extra === "margin" ) {
+ val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ] ) ) || 0;
+ } else {
+ val -= parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ }
+ }
+
+ return val + "px";
+ }
+
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+ }
+
+ // Computed unit is not pixels. Stop here and return.
+ if ( rnumnonpx.test(val) ) {
+ return val;
+ }
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+
+ // Add padding, border, margin
+ if ( extra ) {
+ for ( ; i < len; i += 2 ) {
+ val += parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
+ if ( extra !== "padding" ) {
+ val += parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ if ( extra === "margin" ) {
+ val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ]) ) || 0;
+ }
+ }
+ }
+
+ return val + "px";
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+ if ( elem.offsetWidth !== 0 ) {
+ return getWidthOrHeight( elem, name, extra );
+ } else {
+ return jQuery.swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, name, extra );
+ });
+ }
+ }
+ },
+
+ set: function( elem, value ) {
+ return rnum.test( value ) ?
+ value + "px" :
+ 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
+ return jQuery.swap( elem, { "display": "inline-block" }, function() {
+ if ( computed ) {
+ return curCSS( elem, "margin-right" );
+ } else {
+ return elem.style.marginRight;
+ }
+ });
+ }
+ };
+ }
+});
+
+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 );
+ };
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i,
+
+ // assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ],
+ expanded = {};
+
+ for ( i = 0; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+});
+
+
+
+
+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; charset=UTF-8",
+ 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 prefilter, 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" ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && jQuery.type( 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 = ( typeof s.data === "string" ) && /^application\/x\-www\-form\-urlencoded/.test( s.contentType );
+
+ 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;
+ }
+
+ // When requesting binary data, IE6-9 will throw an exception
+ // on any attempt to access responseText (#11426)
+ try {
+ responses.text = xhr.responseText;
+ } catch( _ ) {
+ }
+
+ // 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.contains( elem.ownerDocument.documentElement, elem ) ) {
+ 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, hooks, replace,
+ parts, start, end, unit,
+ method;
+
+ // will store per property easing and be used to determine when an animation is complete
+ opt.animatedProperties = {};
+
+ // first pass over propertys to expand / normalize
+ for ( p in prop ) {
+ name = jQuery.camelCase( p );
+ if ( p !== name ) {
+ prop[ name ] = prop[ p ];
+ delete prop[ p ];
+ }
+
+ if ( ( hooks = jQuery.cssHooks[ name ] ) && "expand" in hooks ) {
+ replace = hooks.expand( prop[ name ] );
+ delete prop[ name ];
+
+ // not quite $.extend, this wont overwrite keys already present.
+ // also - reusing 'p' from above because we have the correct "name"
+ for ( p in replace ) {
+ if ( ! ( p in prop ) ) {
+ prop[ p ] = replace[ p ];
+ }
+ }
+ }
+ }
+
+ for ( name in prop ) {
+ 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 ) {
+ return p;
+ },
+ swing: function( p ) {
+ return ( -Math.cos( p*Math.PI ) / 2 ) + 0.5;
+ }
+ },
+
+ 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 ( jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
+ if ( self.options.hide ) {
+ jQuery._data( self.elem, "fxshow" + self.prop, self.start );
+ } else if ( self.options.show ) {
+ jQuery._data( self.elem, "fxshow" + self.prop, self.end );
+ }
+ }
+ };
+
+ 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;
+ }
+ }
+ }
+});
+
+// Ensure props that can't be negative don't go there on undershoot easing
+jQuery.each( fxAttrs.concat.apply( [], fxAttrs ), function( i, prop ) {
+ // exclude marginTop, marginLeft, marginBottom and marginRight from this list
+ if ( prop.indexOf( "margin" ) ) {
+ 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( ( jQuery.support.boxModel ? "<!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 getOffset,
+ rtable = /^t(?:able|d|h)$/i,
+ rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+ getOffset = function( elem, doc, docElem, box ) {
+ try {
+ box = elem.getBoundingClientRect();
+ } catch(e) {}
+
+ // 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 {
+ getOffset = function( elem, doc, docElem ) {
+ var computedStyle,
+ offsetParent = elem.offsetParent,
+ prevOffsetParent = elem,
+ 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.fn.offset = function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ var elem = this[0],
+ doc = elem && elem.ownerDocument;
+
+ if ( !doc ) {
+ return null;
+ }
+
+ if ( elem === doc.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ return getOffset( elem, doc, doc.documentElement );
+};
+
+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( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+ var top = /Y/.test( prop );
+
+ jQuery.fn[ method ] = function( val ) {
+ return jQuery.access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
+
+ if ( val === undefined ) {
+ return win ? (prop in win) ? win[ prop ] :
+ jQuery.support.boxModel && win.document.documentElement[ method ] ||
+ win.document.body[ method ] :
+ elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : jQuery( win ).scrollLeft(),
+ top ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
+ };
+});
+
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+
+
+
+
+// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ var clientProp = "client" + name,
+ scrollProp = "scroll" + name,
+ offsetProp = "offset" + name;
+
+ // 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( value ) {
+ return jQuery.access( this, function( elem, type, value ) {
+ var doc, docElemProp, orig, ret;
+
+ if ( jQuery.isWindow( elem ) ) {
+ // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+ doc = elem.document;
+ docElemProp = doc.documentElement[ clientProp ];
+ return jQuery.support.boxModel && docElemProp ||
+ doc.body && doc.body[ clientProp ] || docElemProp;
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+ doc = elem.documentElement;
+
+ // when a window > document, IE6 reports a offset[Width/Height] > client[Width/Height]
+ // so we can't use max, as it'll choose the incorrect offset[Width/Height]
+ // instead we use the correct client[Width/Height]
+ // support:IE6
+ if ( doc[ clientProp ] >= doc[ scrollProp ] ) {
+ return doc[ clientProp ];
+ }
+
+ return Math.max(
+ elem.body[ scrollProp ], doc[ scrollProp ],
+ elem.body[ offsetProp ], doc[ offsetProp ]
+ );
+ }
+
+ // Get width or height on the element
+ if ( value === undefined ) {
+ orig = jQuery.css( elem, type );
+ ret = parseFloat( orig );
+ return jQuery.isNumeric( ret ) ? ret : orig;
+ }
+
+ // Set the width or height on the element
+ jQuery( elem ).css( type, value );
+ }, type, value, arguments.length, null );
+ };
+});
+
+
+
+
+// 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 );
diff --git a/tools/infra-dashboard/js/jquery.min.js b/tools/infra-dashboard/js/jquery.min.js
new file mode 100644
index 00000000..fad9ab12
--- /dev/null
+++ b/tools/infra-dashboard/js/jquery.min.js
@@ -0,0 +1,5 @@
+/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */
+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\f]' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function qa(){}qa.prototype=d.filters=d.pseudos,d.setFilters=new qa,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function ra(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){
+return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=L.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var Q=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,R=["Top","Right","Bottom","Left"],S=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=l.createDocumentFragment(),b=a.appendChild(l.createElement("div")),c=l.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||l,d=c.documentElement,e=c.body,a.pageX=b.clientX+(d&&d.scrollLeft||e&&e.scrollLeft||0)-(d&&d.clientLeft||e&&e.clientLeft||0),a.pageY=b.clientY+(d&&d.scrollTop||e&&e.scrollTop||0)-(d&&d.clientTop||e&&e.clientTop||0)),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=W.test(e)?this.mouseHooks:V.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new n.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=l),3===a.target.nodeType&&(a.target=a.target.parentNode),g.filter?g.filter(a,f):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==_()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===_()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=n.extend(new n.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?n.event.trigger(e,null,b):n.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?Z:$):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={isDefaultPrevented:$,isPropagationStopped:$,isImmediatePropagationStopped:$,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=Z,a&&a.preventDefault&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=Z,a&&a.stopPropagation&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=Z,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!n.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.focusinBubbles||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a),!0)};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=L.access(d,b);e||d.addEventListener(a,c,!0),L.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=L.access(d,b)-1;e?L.access(d,b,e):(d.removeEventListener(a,c,!0),L.remove(d,b))}}}),n.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(g in a)this.on(g,b,c,a[g],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=$;else if(!d)return this;return 1===e&&(f=d,d=function(a){return n().off(a),f.apply(this,arguments)},d.guid=f.guid||(f.guid=n.guid++)),this.each(function(){n.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=$),this.each(function(){n.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}});var aa=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ia={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1></$2>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=qa[0].contentDocument,b.write(),b.close(),c=sa(a,b),qa.detach()),ra[a]=c),c}var ua=/^margin/,va=new RegExp("^("+Q+")(?!px)[a-z%]+$","i"),wa=function(b){return b.ownerDocument.defaultView.opener?b.ownerDocument.defaultView.getComputedStyle(b,null):a.getComputedStyle(b,null)};function xa(a,b,c){var d,e,f,g,h=a.style;return c=c||wa(a),c&&(g=c.getPropertyValue(b)||c[b]),c&&(""!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),va.test(g)&&ua.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function ya(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d=l.documentElement,e=l.createElement("div"),f=l.createElement("div");if(f.style){f.style.backgroundClip="content-box",f.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===f.style.backgroundClip,e.style.cssText="border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;position:absolute",e.appendChild(f);function g(){f.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",f.innerHTML="",d.appendChild(e);var g=a.getComputedStyle(f,null);b="1%"!==g.top,c="4px"===g.width,d.removeChild(e)}a.getComputedStyle&&n.extend(k,{pixelPosition:function(){return g(),b},boxSizingReliable:function(){return null==c&&g(),c},reliableMarginRight:function(){var b,c=f.appendChild(l.createElement("div"));return c.style.cssText=f.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",f.style.width="1px",d.appendChild(e),b=!parseFloat(a.getComputedStyle(c,null).marginRight),d.removeChild(e),f.removeChild(c),b}})}}(),n.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var za=/^(none|table(?!-c[ea]).+)/,Aa=new RegExp("^("+Q+")(.*)$","i"),Ba=new RegExp("^([+-])=("+Q+")","i"),Ca={position:"absolute",visibility:"hidden",display:"block"},Da={letterSpacing:"0",fontWeight:"400"},Ea=["Webkit","O","Moz","ms"];function Fa(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),d=b,e=Ea.length;while(e--)if(b=Ea[e]+c,b in a)return b;return d}function Ga(a,b,c){var d=Aa.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Ha(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+R[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+R[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+R[f]+"Width",!0,e))):(g+=n.css(a,"padding"+R[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+R[f]+"Width",!0,e)));return g}function Ia(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=wa(a),g="border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=xa(a,b,f),(0>e||null==e)&&(e=a.style[b]),va.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Ha(a,b,c||(g?"border":"content"),d,f)+"px"}function Ja(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=L.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&S(d)&&(f[g]=L.access(d,"olddisplay",ta(d.nodeName)))):(e=S(d),"none"===c&&e||L.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=xa(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Fa(i,h)),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=Ba.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(n.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||n.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Fa(a.style,h)),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=xa(a,b,d)),"normal"===e&&b in Da&&(e=Da[b]),""===c||c?(f=parseFloat(e),c===!0||n.isNumeric(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?za.test(n.css(a,"display"))&&0===a.offsetWidth?n.swap(a,Ca,function(){return Ia(a,b,d)}):Ia(a,b,d):void 0},set:function(a,c,d){var e=d&&wa(a);return Ga(a,c,d?Ha(a,b,d,"border-box"===n.css(a,"boxSizing",!1,e),e):0)}}}),n.cssHooks.marginRight=ya(k.reliableMarginRight,function(a,b){return b?n.swap(a,{display:"inline-block"},xa,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+R[d]+b]=f[d]||f[d-2]||f[0];return e}},ua.test(a)||(n.cssHooks[a+b].set=Ga)}),n.fn.extend({css:function(a,b){return J(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=wa(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Ja(this,!0)},hide:function(){return Ja(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){S(this)?n(this).show():n(this).hide()})}});function Ka(a,b,c,d,e){return new Ka.prototype.init(a,b,c,d,e)}n.Tween=Ka,Ka.prototype={constructor:Ka,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Ka.propHooks[this.prop];return a&&a.get?a.get(this):Ka.propHooks._default.get(this)},run:function(a){var b,c=Ka.propHooks[this.prop];return this.options.duration?this.pos=b=n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ka.propHooks._default.set(this),this}},Ka.prototype.init.prototype=Ka.prototype,Ka.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[n.cssProps[a.prop]]||n.cssHooks[a.prop])?n.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Ka.propHooks.scrollTop=Ka.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},n.fx=Ka.prototype.init,n.fx.step={};var La,Ma,Na=/^(?:toggle|show|hide)$/,Oa=new RegExp("^(?:([+-])=|)("+Q+")([a-z%]*)$","i"),Pa=/queueHooks$/,Qa=[Va],Ra={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=Oa.exec(b),f=e&&e[3]||(n.cssNumber[a]?"":"px"),g=(n.cssNumber[a]||"px"!==f&&+d)&&Oa.exec(n.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,n.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function Sa(){return setTimeout(function(){La=void 0}),La=n.now()}function Ta(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ua(a,b,c){for(var d,e=(Ra[b]||[]).concat(Ra["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Va(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&S(a),q=L.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?L.get(a,"olddisplay")||ta(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Na.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?ta(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=L.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;L.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ua(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function Wa(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function Xa(a,b,c){var d,e,f=0,g=Qa.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=La||Sa(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:La||Sa(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(Wa(k,j.opts.specialEasing);g>f;f++)if(d=Qa[f].call(j,a,k,j.opts))return d;return n.map(k,Ua,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(Xa,{tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],Ra[c]=Ra[c]||[],Ra[c].unshift(b)},prefilter:function(a,b){b?Qa.unshift(a):Qa.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(S).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=Xa(this,n.extend({},a),f);(e||L.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=L.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Pa.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=L.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Ta(b,!0),a,d,e)}}),n.each({slideDown:Ta("show"),slideUp:Ta("hide"),slideToggle:Ta("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(La=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),La=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Ma||(Ma=setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){clearInterval(Ma),Ma=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(a,b){return a=n.fx?n.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a=l.createElement("input"),b=l.createElement("select"),c=b.appendChild(l.createElement("option"));a.type="checkbox",k.checkOn=""!==a.value,k.optSelected=c.selected,b.disabled=!0,k.optDisabled=!c.disabled,a=l.createElement("input"),a.value="t",a.type="radio",k.radioValue="t"===a.value}();var Ya,Za,$a=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return J(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===U?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),d=n.attrHooks[b]||(n.expr.match.bool.test(b)?Za:Ya)),
+void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=n.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void n.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),Za={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=$a[b]||n.find.attr;$a[b]=function(a,b,d){var e,f;return d||(f=$a[b],$a[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$a[b]=f),e}});var _a=/^(?:input|select|textarea|button)$/i;n.fn.extend({prop:function(a,b){return J(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!n.isXMLDoc(a),f&&(b=n.propFix[b]||b,e=n.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute("tabindex")||_a.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),k.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var ab=/[\t\r\n\f]/g;n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h="string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ab," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=n.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||"string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ab," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?n.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(n.isFunction(a)?function(c){n(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=n(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||"boolean"===c)&&(this.className&&L.set(this,"__className__",this.className),this.className=this.className||a===!1?"":L.get(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ab," ").indexOf(b)>=0)return!0;return!1}});var bb=/\r/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(bb,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(d.value,f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>=0:void 0}},k.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var cb=n.now(),db=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(a){var b,c;if(!a||"string"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,"text/xml")}catch(d){b=void 0}return(!b||b.getElementsByTagName("parsererror").length)&&n.error("Invalid XML: "+a),b};var eb=/#.*$/,fb=/([?&])_=[^&]*/,gb=/^(.*?):[ \t]*([^\r\n]*)$/gm,hb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,ib=/^(?:GET|HEAD)$/,jb=/^\/\//,kb=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,lb={},mb={},nb="*/".concat("*"),ob=a.location.href,pb=kb.exec(ob.toLowerCase())||[];function qb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function rb(a,b,c,d){var e={},f=a===mb;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function sb(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function tb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function ub(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:ob,type:"GET",isLocal:hb.test(pb[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":nb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?sb(sb(a,n.ajaxSettings),b):sb(n.ajaxSettings,a)},ajaxPrefilter:qb(lb),ajaxTransport:qb(mb),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=n.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?n(l):n.event,o=n.Deferred(),p=n.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!f){f={};while(b=gb.exec(e))f[b[1].toLowerCase()]=b[2]}b=f[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?e:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return c&&c.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||ob)+"").replace(eb,"").replace(jb,pb[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=n.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(h=kb.exec(k.url.toLowerCase()),k.crossDomain=!(!h||h[1]===pb[1]&&h[2]===pb[2]&&(h[3]||("http:"===h[1]?"80":"443"))===(pb[3]||("http:"===pb[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=n.param(k.data,k.traditional)),rb(lb,k,b,v),2===t)return v;i=n.event&&k.global,i&&0===n.active++&&n.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!ib.test(k.type),d=k.url,k.hasContent||(k.data&&(d=k.url+=(db.test(d)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=fb.test(d)?d.replace(fb,"$1_="+cb++):d+(db.test(d)?"&":"?")+"_="+cb++)),k.ifModified&&(n.lastModified[d]&&v.setRequestHeader("If-Modified-Since",n.lastModified[d]),n.etag[d]&&v.setRequestHeader("If-None-Match",n.etag[d])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+nb+"; q=0.01":""):k.accepts["*"]);for(j in k.headers)v.setRequestHeader(j,k.headers[j]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(j in{success:1,error:1,complete:1})v[j](k[j]);if(c=rb(mb,k,b,v)){v.readyState=1,i&&m.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,c.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=void 0,e=h||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,f&&(u=tb(k,v,f)),u=ub(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(n.lastModified[d]=w),w=v.getResponseHeader("etag"),w&&(n.etag[d]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,i&&m.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),i&&(m.trigger("ajaxComplete",[v,k]),--n.active||n.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return this.each(n.isFunction(a)?function(b){n(this).wrapInner(a.call(this,b))}:function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0},n.expr.filters.visible=function(a){return!n.expr.filters.hidden(a)};var vb=/%20/g,wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&").replace(vb,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!T.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var Bb=0,Cb={},Db={0:200,1223:204},Eb=n.ajaxSettings.xhr();a.attachEvent&&a.attachEvent("onunload",function(){for(var a in Cb)Cb[a]()}),k.cors=!!Eb&&"withCredentials"in Eb,k.ajax=Eb=!!Eb,n.ajaxTransport(function(a){var b;return k.cors||Eb&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++Bb;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete Cb[g],b=f.onload=f.onerror=null,"abort"===a?f.abort():"error"===a?d(f.status,f.statusText):d(Db[f.status]||f.status,f.statusText,"string"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b("error"),b=Cb[g]=b("abort");try{f.send(a.hasContent&&a.data||null)}catch(h){if(b)throw h}},abort:function(){b&&b()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=n("<script>").prop({async:!0,charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&e("error"===a.type?404:200,a.type)}),l.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Fb=[],Gb=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Fb.pop()||n.expando+"_"+cb++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Gb.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Gb.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Gb,"$1"+e):b.jsonp!==!1&&(b.url+=(db.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Fb.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||l;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=n.buildFragment([a],b,e),e&&e.length&&n(e).remove(),n.merge([],d.childNodes))};var Hb=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Hb)return Hb.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e,dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,f||[a.responseText,b,a])}),this},n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};var Ib=a.document.documentElement;function Jb(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(typeof d.getBoundingClientRect!==U&&(e=d.getBoundingClientRect()),c=Jb(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0),d.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||Ib;while(a&&!n.nodeName(a,"html")&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Ib})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b,c){var d="pageYOffset"===c;n.fn[b]=function(e){return J(this,function(b,e,f){var g=Jb(b);return void 0===f?g?g[c]:b[e]:void(g?g.scrollTo(d?a.pageXOffset:f,d?f:a.pageYOffset):b[e]=f)},b,e,arguments.length,null)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=ya(k.pixelPosition,function(a,c){return c?(c=xa(a,b),va.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return J(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Kb=a.jQuery,Lb=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Lb),b&&a.jQuery===n&&(a.jQuery=Kb),n},typeof b===U&&(a.jQuery=a.$=n),n});
+//# sourceMappingURL=jquery.min.map \ No newline at end of file
diff --git a/tools/infra-dashboard/js/modernizr.js b/tools/infra-dashboard/js/modernizr.js
new file mode 100644
index 00000000..c3a5741a
--- /dev/null
+++ b/tools/infra-dashboard/js/modernizr.js
@@ -0,0 +1,4 @@
+/* Modernizr 2.8.3 (Custom Build) | MIT & BSD
+ * Build: http://modernizr.com/download/#-svg-shiv-mq-cssclasses-teststyles-load
+ */
+;window.Modernizr=function(a,b,c){function x(a){j.cssText=a}function y(a,b){return x(prefixes.join(a+";")+(b||""))}function z(a,b){return typeof a===b}function A(a,b){return!!~(""+a).indexOf(b)}function B(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:z(f,"function")?f.bind(d||b):f}return!1}var d="2.8.3",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m={svg:"http://www.w3.org/2000/svg"},n={},o={},p={},q=[],r=q.slice,s,t=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["&#173;",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},u=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b)&&c(b).matches||!1;var d;return t("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},v={}.hasOwnProperty,w;!z(v,"undefined")&&!z(v.call,"undefined")?w=function(a,b){return v.call(a,b)}:w=function(a,b){return b in a&&z(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=r.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(r.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(r.call(arguments)))};return e}),n.svg=function(){return!!b.createElementNS&&!!b.createElementNS(m.svg,"svg").createSVGRect};for(var C in n)w(n,C)&&(s=C.toLowerCase(),e[s]=n[C](),q.push((e[s]?"":"no-")+s));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)w(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},x(""),i=k=null,function(a,b){function l(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function m(){var a=s.elements;return typeof a=="string"?a.split(" "):a}function n(a){var b=j[a[h]];return b||(b={},i++,a[h]=i,j[i]=b),b}function o(a,c,d){c||(c=b);if(k)return c.createElement(a);d||(d=n(c));var g;return d.cache[a]?g=d.cache[a].cloneNode():f.test(a)?g=(d.cache[a]=d.createElem(a)).cloneNode():g=d.createElem(a),g.canHaveChildren&&!e.test(a)&&!g.tagUrn?d.frag.appendChild(g):g}function p(a,c){a||(a=b);if(k)return a.createDocumentFragment();c=c||n(a);var d=c.frag.cloneNode(),e=0,f=m(),g=f.length;for(;e<g;e++)d.createElement(f[e]);return d}function q(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return s.shivMethods?o(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(s,b.frag)}function r(a){a||(a=b);var c=n(a);return s.shivCSS&&!g&&!c.hasCSS&&(c.hasCSS=!!l(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),k||q(a,c),a}var c="3.7.0",d=a.html5||{},e=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,f=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,g,h="_html5shiv",i=0,j={},k;(function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",g="hidden"in a,k=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){g=!0,k=!0}})();var s={elements:d.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:c,shivCSS:d.shivCSS!==!1,supportsUnknownElements:k,shivMethods:d.shivMethods!==!1,type:"default",shivDocument:r,createElement:o,createDocumentFragment:p};a.html5=s,r(b)}(this,b),e._version=d,e.mq=u,e.testStyles=t,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+q.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))}; \ No newline at end of file
diff --git a/tools/infra-dashboard/js/moment.min.js b/tools/infra-dashboard/js/moment.min.js
new file mode 100644
index 00000000..d301ddbb
--- /dev/null
+++ b/tools/infra-dashboard/js/moment.min.js
@@ -0,0 +1,7 @@
+//! moment.js
+//! version : 2.13.0
+//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
+//! license : MIT
+//! momentjs.com
+!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return fd.apply(null,arguments)}function b(a){fd=a}function c(a){return a instanceof Array||"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c<a.length;++c)d.push(b(a[c],c));return d}function f(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function g(a,b){for(var c in b)f(b,c)&&(a[c]=b[c]);return f(b,"toString")&&(a.toString=b.toString),f(b,"valueOf")&&(a.valueOf=b.valueOf),a}function h(a,b,c,d){return Ja(a,b,c,d,!0).utc()}function i(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],meridiem:null}}function j(a){return null==a._pf&&(a._pf=i()),a._pf}function k(a){if(null==a._isValid){var b=j(a),c=gd.call(b.parsedDateParts,function(a){return null!=a});a._isValid=!isNaN(a._d.getTime())&&b.overflow<0&&!b.empty&&!b.invalidMonth&&!b.invalidWeekday&&!b.nullInput&&!b.invalidFormat&&!b.userInvalidated&&(!b.meridiem||b.meridiem&&c),a._strict&&(a._isValid=a._isValid&&0===b.charsLeftOver&&0===b.unusedTokens.length&&void 0===b.bigHour)}return a._isValid}function l(a){var b=h(NaN);return null!=a?g(j(b),a):j(b).userInvalidated=!0,b}function m(a){return void 0===a}function n(a,b){var c,d,e;if(m(b._isAMomentObject)||(a._isAMomentObject=b._isAMomentObject),m(b._i)||(a._i=b._i),m(b._f)||(a._f=b._f),m(b._l)||(a._l=b._l),m(b._strict)||(a._strict=b._strict),m(b._tzm)||(a._tzm=b._tzm),m(b._isUTC)||(a._isUTC=b._isUTC),m(b._offset)||(a._offset=b._offset),m(b._pf)||(a._pf=j(b)),m(b._locale)||(a._locale=b._locale),hd.length>0)for(c in hd)d=hd[c],e=b[d],m(e)||(a[d]=e);return a}function o(b){n(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),id===!1&&(id=!0,a.updateOffset(this),id=!1)}function p(a){return a instanceof o||null!=a&&null!=a._isAMomentObject}function q(a){return 0>a?Math.ceil(a):Math.floor(a)}function r(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=q(b)),c}function s(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&r(a[d])!==r(b[d]))&&g++;return g+f}function t(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function u(b,c){var d=!0;return g(function(){return null!=a.deprecationHandler&&a.deprecationHandler(null,b),d&&(t(b+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),d=!1),c.apply(this,arguments)},c)}function v(b,c){null!=a.deprecationHandler&&a.deprecationHandler(b,c),jd[b]||(t(c),jd[b]=!0)}function w(a){return a instanceof Function||"[object Function]"===Object.prototype.toString.call(a)}function x(a){return"[object Object]"===Object.prototype.toString.call(a)}function y(a){var b,c;for(c in a)b=a[c],w(b)?this[c]=b:this["_"+c]=b;this._config=a,this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function z(a,b){var c,d=g({},a);for(c in b)f(b,c)&&(x(a[c])&&x(b[c])?(d[c]={},g(d[c],a[c]),g(d[c],b[c])):null!=b[c]?d[c]=b[c]:delete d[c]);return d}function A(a){null!=a&&this.set(a)}function B(a){return a?a.toLowerCase().replace("_","-"):a}function C(a){for(var b,c,d,e,f=0;f<a.length;){for(e=B(a[f]).split("-"),b=e.length,c=B(a[f+1]),c=c?c.split("-"):null;b>0;){if(d=D(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&s(e,c,!0)>=b-1)break;b--}f++}return null}function D(a){var b=null;if(!nd[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=ld._abbr,require("./locale/"+a),E(b)}catch(c){}return nd[a]}function E(a,b){var c;return a&&(c=m(b)?H(a):F(a,b),c&&(ld=c)),ld._abbr}function F(a,b){return null!==b?(b.abbr=a,null!=nd[a]?(v("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale"),b=z(nd[a]._config,b)):null!=b.parentLocale&&(null!=nd[b.parentLocale]?b=z(nd[b.parentLocale]._config,b):v("parentLocaleUndefined","specified parentLocale is not defined yet")),nd[a]=new A(b),E(a),nd[a]):(delete nd[a],null)}function G(a,b){if(null!=b){var c;null!=nd[a]&&(b=z(nd[a]._config,b)),c=new A(b),c.parentLocale=nd[a],nd[a]=c,E(a)}else null!=nd[a]&&(null!=nd[a].parentLocale?nd[a]=nd[a].parentLocale:null!=nd[a]&&delete nd[a]);return nd[a]}function H(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return ld;if(!c(a)){if(b=D(a))return b;a=[a]}return C(a)}function I(){return kd(nd)}function J(a,b){var c=a.toLowerCase();od[c]=od[c+"s"]=od[b]=a}function K(a){return"string"==typeof a?od[a]||od[a.toLowerCase()]:void 0}function L(a){var b,c,d={};for(c in a)f(a,c)&&(b=K(c),b&&(d[b]=a[c]));return d}function M(b,c){return function(d){return null!=d?(O(this,b,d),a.updateOffset(this,c),this):N(this,b)}}function N(a,b){return a.isValid()?a._d["get"+(a._isUTC?"UTC":"")+b]():NaN}function O(a,b,c){a.isValid()&&a._d["set"+(a._isUTC?"UTC":"")+b](c)}function P(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=K(a),w(this[a]))return this[a](b);return this}function Q(a,b,c){var d=""+Math.abs(a),e=b-d.length,f=a>=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function R(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(sd[a]=e),b&&(sd[b[0]]=function(){return Q(e.apply(this,arguments),b[1],b[2])}),c&&(sd[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function S(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function T(a){var b,c,d=a.match(pd);for(b=0,c=d.length;c>b;b++)sd[d[b]]?d[b]=sd[d[b]]:d[b]=S(d[b]);return function(b){var e,f="";for(e=0;c>e;e++)f+=d[e]instanceof Function?d[e].call(b,a):d[e];return f}}function U(a,b){return a.isValid()?(b=V(b,a.localeData()),rd[b]=rd[b]||T(b),rd[b](a)):a.localeData().invalidDate()}function V(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(qd.lastIndex=0;d>=0&&qd.test(a);)a=a.replace(qd,c),qd.lastIndex=0,d-=1;return a}function W(a,b,c){Kd[a]=w(b)?b:function(a,d){return a&&c?c:b}}function X(a,b){return f(Kd,a)?Kd[a](b._strict,b._locale):new RegExp(Y(a))}function Y(a){return Z(a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}))}function Z(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function $(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=r(a)}),c=0;c<a.length;c++)Ld[a[c]]=d}function _(a,b){$(a,function(a,c,d,e){d._w=d._w||{},b(a,d._w,d,e)})}function aa(a,b,c){null!=b&&f(Ld,a)&&Ld[a](b,c._a,c,a)}function ba(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function ca(a,b){return c(this._months)?this._months[a.month()]:this._months[Vd.test(b)?"format":"standalone"][a.month()]}function da(a,b){return c(this._monthsShort)?this._monthsShort[a.month()]:this._monthsShort[Vd.test(b)?"format":"standalone"][a.month()]}function ea(a,b,c){var d,e,f,g=a.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],d=0;12>d;++d)f=h([2e3,d]),this._shortMonthsParse[d]=this.monthsShort(f,"").toLocaleLowerCase(),this._longMonthsParse[d]=this.months(f,"").toLocaleLowerCase();return c?"MMM"===b?(e=md.call(this._shortMonthsParse,g),-1!==e?e:null):(e=md.call(this._longMonthsParse,g),-1!==e?e:null):"MMM"===b?(e=md.call(this._shortMonthsParse,g),-1!==e?e:(e=md.call(this._longMonthsParse,g),-1!==e?e:null)):(e=md.call(this._longMonthsParse,g),-1!==e?e:(e=md.call(this._shortMonthsParse,g),-1!==e?e:null))}function fa(a,b,c){var d,e,f;if(this._monthsParseExact)return ea.call(this,a,b,c);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),d=0;12>d;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function ga(a,b){var c;if(!a.isValid())return a;if("string"==typeof b)if(/^\d+$/.test(b))b=r(b);else if(b=a.localeData().monthsParse(b),"number"!=typeof b)return a;return c=Math.min(a.date(),ba(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a}function ha(b){return null!=b?(ga(this,b),a.updateOffset(this,!0),this):N(this,"Month")}function ia(){return ba(this.year(),this.month())}function ja(a){return this._monthsParseExact?(f(this,"_monthsRegex")||la.call(this),a?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&a?this._monthsShortStrictRegex:this._monthsShortRegex}function ka(a){return this._monthsParseExact?(f(this,"_monthsRegex")||la.call(this),a?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&a?this._monthsStrictRegex:this._monthsRegex}function la(){function a(a,b){return b.length-a.length}var b,c,d=[],e=[],f=[];for(b=0;12>b;b++)c=h([2e3,b]),d.push(this.monthsShort(c,"")),e.push(this.months(c,"")),f.push(this.months(c,"")),f.push(this.monthsShort(c,""));for(d.sort(a),e.sort(a),f.sort(a),b=0;12>b;b++)d[b]=Z(d[b]),e[b]=Z(e[b]),f[b]=Z(f[b]);this._monthsRegex=new RegExp("^("+f.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+e.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+d.join("|")+")","i")}function ma(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[Nd]<0||c[Nd]>11?Nd:c[Od]<1||c[Od]>ba(c[Md],c[Nd])?Od:c[Pd]<0||c[Pd]>24||24===c[Pd]&&(0!==c[Qd]||0!==c[Rd]||0!==c[Sd])?Pd:c[Qd]<0||c[Qd]>59?Qd:c[Rd]<0||c[Rd]>59?Rd:c[Sd]<0||c[Sd]>999?Sd:-1,j(a)._overflowDayOfYear&&(Md>b||b>Od)&&(b=Od),j(a)._overflowWeeks&&-1===b&&(b=Td),j(a)._overflowWeekday&&-1===b&&(b=Ud),j(a).overflow=b),a}function na(a){var b,c,d,e,f,g,h=a._i,i=$d.exec(h)||_d.exec(h);if(i){for(j(a).iso=!0,b=0,c=be.length;c>b;b++)if(be[b][1].exec(i[1])){e=be[b][0],d=be[b][2]!==!1;break}if(null==e)return void(a._isValid=!1);if(i[3]){for(b=0,c=ce.length;c>b;b++)if(ce[b][1].exec(i[3])){f=(i[2]||" ")+ce[b][0];break}if(null==f)return void(a._isValid=!1)}if(!d&&null!=f)return void(a._isValid=!1);if(i[4]){if(!ae.exec(i[4]))return void(a._isValid=!1);g="Z"}a._f=e+(f||"")+(g||""),Ca(a)}else a._isValid=!1}function oa(b){var c=de.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(na(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function pa(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 100>a&&a>=0&&isFinite(h.getFullYear())&&h.setFullYear(a),h}function qa(a){var b=new Date(Date.UTC.apply(null,arguments));return 100>a&&a>=0&&isFinite(b.getUTCFullYear())&&b.setUTCFullYear(a),b}function ra(a){return sa(a)?366:365}function sa(a){return a%4===0&&a%100!==0||a%400===0}function ta(){return sa(this.year())}function ua(a,b,c){var d=7+b-c,e=(7+qa(a,0,d).getUTCDay()-b)%7;return-e+d-1}function va(a,b,c,d,e){var f,g,h=(7+c-d)%7,i=ua(a,d,e),j=1+7*(b-1)+h+i;return 0>=j?(f=a-1,g=ra(f)+j):j>ra(a)?(f=a+1,g=j-ra(a)):(f=a,g=j),{year:f,dayOfYear:g}}function wa(a,b,c){var d,e,f=ua(a.year(),b,c),g=Math.floor((a.dayOfYear()-f-1)/7)+1;return 1>g?(e=a.year()-1,d=g+xa(e,b,c)):g>xa(a.year(),b,c)?(d=g-xa(a.year(),b,c),e=a.year()+1):(e=a.year(),d=g),{week:d,year:e}}function xa(a,b,c){var d=ua(a,b,c),e=ua(a+1,b,c);return(ra(a)-d+e)/7}function ya(a,b,c){return null!=a?a:null!=b?b:c}function za(b){var c=new Date(a.now());return b._useUTC?[c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate()]:[c.getFullYear(),c.getMonth(),c.getDate()]}function Aa(a){var b,c,d,e,f=[];if(!a._d){for(d=za(a),a._w&&null==a._a[Od]&&null==a._a[Nd]&&Ba(a),a._dayOfYear&&(e=ya(a._a[Md],d[Md]),a._dayOfYear>ra(e)&&(j(a)._overflowDayOfYear=!0),c=qa(e,0,a._dayOfYear),a._a[Nd]=c.getUTCMonth(),a._a[Od]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[Pd]&&0===a._a[Qd]&&0===a._a[Rd]&&0===a._a[Sd]&&(a._nextDay=!0,a._a[Pd]=0),a._d=(a._useUTC?qa:pa).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[Pd]=24)}}function Ba(a){var b,c,d,e,f,g,h,i;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=ya(b.GG,a._a[Md],wa(Ka(),1,4).year),d=ya(b.W,1),e=ya(b.E,1),(1>e||e>7)&&(i=!0)):(f=a._locale._week.dow,g=a._locale._week.doy,c=ya(b.gg,a._a[Md],wa(Ka(),f,g).year),d=ya(b.w,1),null!=b.d?(e=b.d,(0>e||e>6)&&(i=!0)):null!=b.e?(e=b.e+f,(b.e<0||b.e>6)&&(i=!0)):e=f),1>d||d>xa(c,f,g)?j(a)._overflowWeeks=!0:null!=i?j(a)._overflowWeekday=!0:(h=va(c,d,e,f,g),a._a[Md]=h.year,a._dayOfYear=h.dayOfYear)}function Ca(b){if(b._f===a.ISO_8601)return void na(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=V(b._f,b._locale).match(pd)||[],c=0;c<e.length;c++)f=e[c],d=(h.match(X(f,b))||[])[0],d&&(g=h.substr(0,h.indexOf(d)),g.length>0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),sd[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),aa(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[Pd]<=12&&b._a[Pd]>0&&(j(b).bigHour=void 0),j(b).parsedDateParts=b._a.slice(0),j(b).meridiem=b._meridiem,b._a[Pd]=Da(b._locale,b._a[Pd],b._meridiem),Aa(b),ma(b)}function Da(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function Ea(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;e<a._f.length;e++)f=0,b=n({},a),null!=a._useUTC&&(b._useUTC=a._useUTC),b._f=a._f[e],Ca(b),k(b)&&(f+=j(b).charsLeftOver,f+=10*j(b).unusedTokens.length,j(b).score=f,(null==d||d>f)&&(d=f,c=b));g(a,c||b)}function Fa(a){if(!a._d){var b=L(a._i);a._a=e([b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],function(a){return a&&parseInt(a,10)}),Aa(a)}}function Ga(a){var b=new o(ma(Ha(a)));return b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b}function Ha(a){var b=a._i,e=a._f;return a._locale=a._locale||H(a._l),null===b||void 0===e&&""===b?l({nullInput:!0}):("string"==typeof b&&(a._i=b=a._locale.preparse(b)),p(b)?new o(ma(b)):(c(e)?Ea(a):e?Ca(a):d(b)?a._d=b:Ia(a),k(a)||(a._d=null),a))}function Ia(b){var f=b._i;void 0===f?b._d=new Date(a.now()):d(f)?b._d=new Date(f.valueOf()):"string"==typeof f?oa(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),Aa(b)):"object"==typeof f?Fa(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function Ja(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,Ga(f)}function Ka(a,b,c,d){return Ja(a,b,c,d,!1)}function La(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Ka();for(d=b[0],e=1;e<b.length;++e)(!b[e].isValid()||b[e][a](d))&&(d=b[e]);return d}function Ma(){var a=[].slice.call(arguments,0);return La("isBefore",a)}function Na(){var a=[].slice.call(arguments,0);return La("isAfter",a)}function Oa(a){var b=L(a),c=b.year||0,d=b.quarter||0,e=b.month||0,f=b.week||0,g=b.day||0,h=b.hour||0,i=b.minute||0,j=b.second||0,k=b.millisecond||0;this._milliseconds=+k+1e3*j+6e4*i+1e3*h*60*60,this._days=+g+7*f,this._months=+e+3*d+12*c,this._data={},this._locale=H(),this._bubble()}function Pa(a){return a instanceof Oa}function Qa(a,b){R(a,0,0,function(){var a=this.utcOffset(),c="+";return 0>a&&(a=-a,c="-"),c+Q(~~(a/60),2)+b+Q(~~a%60,2)})}function Ra(a,b){var c=(b||"").match(a)||[],d=c[c.length-1]||[],e=(d+"").match(ie)||["-",0,0],f=+(60*e[1])+r(e[2]);return"+"===e[0]?f:-f}function Sa(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(p(b)||d(b)?b.valueOf():Ka(b).valueOf())-e.valueOf(),e._d.setTime(e._d.valueOf()+f),a.updateOffset(e,!1),e):Ka(b).local()}function Ta(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Ua(b,c){var d,e=this._offset||0;return this.isValid()?null!=b?("string"==typeof b?b=Ra(Hd,b):Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ta(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?jb(this,db(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ta(this):null!=b?this:NaN}function Va(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Wa(a){return this.utcOffset(0,a)}function Xa(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ta(this),"m")),this}function Ya(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ra(Gd,this._i)),this}function Za(a){return this.isValid()?(a=a?Ka(a).utcOffset():0,(this.utcOffset()-a)%60===0):!1}function $a(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function _a(){if(!m(this._isDSTShifted))return this._isDSTShifted;var a={};if(n(a,this),a=Ha(a),a._a){var b=a._isUTC?h(a._a):Ka(a._a);this._isDSTShifted=this.isValid()&&s(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function ab(){return this.isValid()?!this._isUTC:!1}function bb(){return this.isValid()?this._isUTC:!1}function cb(){return this.isValid()?this._isUTC&&0===this._offset:!1}function db(a,b){var c,d,e,g=a,h=null;return Pa(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=je.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:r(h[Od])*c,h:r(h[Pd])*c,m:r(h[Qd])*c,s:r(h[Rd])*c,ms:r(h[Sd])*c}):(h=ke.exec(a))?(c="-"===h[1]?-1:1,g={y:eb(h[2],c),M:eb(h[3],c),w:eb(h[4],c),d:eb(h[5],c),h:eb(h[6],c),m:eb(h[7],c),s:eb(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=gb(Ka(g.from),Ka(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Oa(g),Pa(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function eb(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function fb(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function gb(a,b){var c;return a.isValid()&&b.isValid()?(b=Sa(b,a),a.isBefore(b)?c=fb(a,b):(c=fb(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c):{milliseconds:0,months:0}}function hb(a){return 0>a?-1*Math.round(-1*a):Math.round(a)}function ib(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(v(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=db(c,d),jb(this,e,a),this}}function jb(b,c,d,e){var f=c._milliseconds,g=hb(c._days),h=hb(c._months);b.isValid()&&(e=null==e?!0:e,f&&b._d.setTime(b._d.valueOf()+f*d),g&&O(b,"Date",N(b,"Date")+g*d),h&&ga(b,N(b,"Month")+h*d),e&&a.updateOffset(b,g||h))}function kb(a,b){var c=a||Ka(),d=Sa(c,this).startOf("day"),e=this.diff(d,"days",!0),f=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse",g=b&&(w(b[f])?b[f]():b[f]);return this.format(g||this.localeData().calendar(f,this,Ka(c)))}function lb(){return new o(this)}function mb(a,b){var c=p(a)?a:Ka(a);return this.isValid()&&c.isValid()?(b=K(m(b)?"millisecond":b),"millisecond"===b?this.valueOf()>c.valueOf():c.valueOf()<this.clone().startOf(b).valueOf()):!1}function nb(a,b){var c=p(a)?a:Ka(a);return this.isValid()&&c.isValid()?(b=K(m(b)?"millisecond":b),"millisecond"===b?this.valueOf()<c.valueOf():this.clone().endOf(b).valueOf()<c.valueOf()):!1}function ob(a,b,c,d){return d=d||"()",("("===d[0]?this.isAfter(a,c):!this.isBefore(a,c))&&(")"===d[1]?this.isBefore(b,c):!this.isAfter(b,c))}function pb(a,b){var c,d=p(a)?a:Ka(a);return this.isValid()&&d.isValid()?(b=K(b||"millisecond"),"millisecond"===b?this.valueOf()===d.valueOf():(c=d.valueOf(),this.clone().startOf(b).valueOf()<=c&&c<=this.clone().endOf(b).valueOf())):!1}function qb(a,b){return this.isSame(a,b)||this.isAfter(a,b)}function rb(a,b){return this.isSame(a,b)||this.isBefore(a,b)}function sb(a,b,c){var d,e,f,g;return this.isValid()?(d=Sa(a,this),d.isValid()?(e=6e4*(d.utcOffset()-this.utcOffset()),b=K(b),"year"===b||"month"===b||"quarter"===b?(g=tb(this,d),"quarter"===b?g/=3:"year"===b&&(g/=12)):(f=this-d,g="second"===b?f/1e3:"minute"===b?f/6e4:"hour"===b?f/36e5:"day"===b?(f-e)/864e5:"week"===b?(f-e)/6048e5:f),c?g:q(g)):NaN):NaN}function tb(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)||0}function ub(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function vb(){var a=this.clone().utc();return 0<a.year()&&a.year()<=9999?w(Date.prototype.toISOString)?this.toDate().toISOString():U(a,"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]"):U(a,"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]")}function wb(b){b||(b=this.isUtc()?a.defaultFormatUtc:a.defaultFormat);var c=U(this,b);return this.localeData().postformat(c)}function xb(a,b){return this.isValid()&&(p(a)&&a.isValid()||Ka(a).isValid())?db({to:this,from:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function yb(a){return this.from(Ka(),a)}function zb(a,b){return this.isValid()&&(p(a)&&a.isValid()||Ka(a).isValid())?db({from:this,to:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function Ab(a){return this.to(Ka(),a)}function Bb(a){var b;return void 0===a?this._locale._abbr:(b=H(a),null!=b&&(this._locale=b),this)}function Cb(){return this._locale}function Db(a){switch(a=K(a)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":case"date":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a&&this.weekday(0),"isoWeek"===a&&this.isoWeekday(1),"quarter"===a&&this.month(3*Math.floor(this.month()/3)),this}function Eb(a){return a=K(a),void 0===a||"millisecond"===a?this:("date"===a&&(a="day"),this.startOf(a).add(1,"isoWeek"===a?"week":a).subtract(1,"ms"))}function Fb(){return this._d.valueOf()-6e4*(this._offset||0)}function Gb(){return Math.floor(this.valueOf()/1e3)}function Hb(){return this._offset?new Date(this.valueOf()):this._d}function Ib(){var a=this;return[a.year(),a.month(),a.date(),a.hour(),a.minute(),a.second(),a.millisecond()]}function Jb(){var a=this;return{years:a.year(),months:a.month(),date:a.date(),hours:a.hours(),minutes:a.minutes(),seconds:a.seconds(),milliseconds:a.milliseconds()}}function Kb(){return this.isValid()?this.toISOString():null}function Lb(){return k(this)}function Mb(){return g({},j(this))}function Nb(){return j(this).overflow}function Ob(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function Pb(a,b){R(0,[a,a.length],0,b)}function Qb(a){return Ub.call(this,a,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)}function Rb(a){return Ub.call(this,a,this.isoWeek(),this.isoWeekday(),1,4)}function Sb(){return xa(this.year(),1,4)}function Tb(){var a=this.localeData()._week;return xa(this.year(),a.dow,a.doy)}function Ub(a,b,c,d,e){var f;return null==a?wa(this,d,e).year:(f=xa(a,d,e),b>f&&(b=f),Vb.call(this,a,b,c,d,e))}function Vb(a,b,c,d,e){var f=va(a,b,c,d,e),g=qa(f.year,0,f.dayOfYear);return this.year(g.getUTCFullYear()),this.month(g.getUTCMonth()),this.date(g.getUTCDate()),this}function Wb(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)}function Xb(a){return wa(a,this._week.dow,this._week.doy).week}function Yb(){return this._week.dow}function Zb(){return this._week.doy}function $b(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function _b(a){var b=wa(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function ac(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function bc(a,b){return c(this._weekdays)?this._weekdays[a.day()]:this._weekdays[this._weekdays.isFormat.test(b)?"format":"standalone"][a.day()]}function cc(a){return this._weekdaysShort[a.day()]}function dc(a){return this._weekdaysMin[a.day()]}function ec(a,b,c){var d,e,f,g=a.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],d=0;7>d;++d)f=h([2e3,1]).day(d),this._minWeekdaysParse[d]=this.weekdaysMin(f,"").toLocaleLowerCase(),this._shortWeekdaysParse[d]=this.weekdaysShort(f,"").toLocaleLowerCase(),this._weekdaysParse[d]=this.weekdays(f,"").toLocaleLowerCase();return c?"dddd"===b?(e=md.call(this._weekdaysParse,g),-1!==e?e:null):"ddd"===b?(e=md.call(this._shortWeekdaysParse,g),-1!==e?e:null):(e=md.call(this._minWeekdaysParse,g),-1!==e?e:null):"dddd"===b?(e=md.call(this._weekdaysParse,g),-1!==e?e:(e=md.call(this._shortWeekdaysParse,g),-1!==e?e:(e=md.call(this._minWeekdaysParse,g),-1!==e?e:null))):"ddd"===b?(e=md.call(this._shortWeekdaysParse,g),-1!==e?e:(e=md.call(this._weekdaysParse,g),-1!==e?e:(e=md.call(this._minWeekdaysParse,g),-1!==e?e:null))):(e=md.call(this._minWeekdaysParse,g),-1!==e?e:(e=md.call(this._weekdaysParse,g),-1!==e?e:(e=md.call(this._shortWeekdaysParse,g),-1!==e?e:null)))}function fc(a,b,c){var d,e,f;if(this._weekdaysParseExact)return ec.call(this,a,b,c);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),d=0;7>d;d++){if(e=h([2e3,1]).day(d),c&&!this._fullWeekdaysParse[d]&&(this._fullWeekdaysParse[d]=new RegExp("^"+this.weekdays(e,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[d]=new RegExp("^"+this.weekdaysShort(e,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[d]=new RegExp("^"+this.weekdaysMin(e,"").replace(".",".?")+"$","i")),this._weekdaysParse[d]||(f="^"+this.weekdays(e,"")+"|^"+this.weekdaysShort(e,"")+"|^"+this.weekdaysMin(e,""),this._weekdaysParse[d]=new RegExp(f.replace(".",""),"i")),c&&"dddd"===b&&this._fullWeekdaysParse[d].test(a))return d;if(c&&"ddd"===b&&this._shortWeekdaysParse[d].test(a))return d;if(c&&"dd"===b&&this._minWeekdaysParse[d].test(a))return d;if(!c&&this._weekdaysParse[d].test(a))return d}}function gc(a){if(!this.isValid())return null!=a?this:NaN;var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=ac(a,this.localeData()),this.add(a-b,"d")):b}function hc(a){if(!this.isValid())return null!=a?this:NaN;var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function ic(a){return this.isValid()?null==a?this.day()||7:this.day(this.day()%7?a:a-7):null!=a?this:NaN}function jc(a){return this._weekdaysParseExact?(f(this,"_weekdaysRegex")||mc.call(this),a?this._weekdaysStrictRegex:this._weekdaysRegex):this._weekdaysStrictRegex&&a?this._weekdaysStrictRegex:this._weekdaysRegex}function kc(a){return this._weekdaysParseExact?(f(this,"_weekdaysRegex")||mc.call(this),a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):this._weekdaysShortStrictRegex&&a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex}function lc(a){return this._weekdaysParseExact?(f(this,"_weekdaysRegex")||mc.call(this),a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):this._weekdaysMinStrictRegex&&a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex}function mc(){function a(a,b){return b.length-a.length}var b,c,d,e,f,g=[],i=[],j=[],k=[];for(b=0;7>b;b++)c=h([2e3,1]).day(b),d=this.weekdaysMin(c,""),e=this.weekdaysShort(c,""),f=this.weekdays(c,""),g.push(d),i.push(e),j.push(f),k.push(d),k.push(e),k.push(f);for(g.sort(a),i.sort(a),j.sort(a),k.sort(a),b=0;7>b;b++)i[b]=Z(i[b]),j[b]=Z(j[b]),k[b]=Z(k[b]);this._weekdaysRegex=new RegExp("^("+k.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+j.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+g.join("|")+")","i")}function nc(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function oc(){return this.hours()%12||12}function pc(){return this.hours()||24}function qc(a,b){R(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function rc(a,b){return b._meridiemParse}function sc(a){return"p"===(a+"").toLowerCase().charAt(0)}function tc(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function uc(a,b){b[Sd]=r(1e3*("0."+a))}function vc(){return this._isUTC?"UTC":""}function wc(){return this._isUTC?"Coordinated Universal Time":""}function xc(a){return Ka(1e3*a)}function yc(){return Ka.apply(null,arguments).parseZone()}function zc(a,b,c){var d=this._calendar[a];return w(d)?d.call(b,c):d}function Ac(a){var b=this._longDateFormat[a],c=this._longDateFormat[a.toUpperCase()];return b||!c?b:(this._longDateFormat[a]=c.replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a])}function Bc(){return this._invalidDate}function Cc(a){return this._ordinal.replace("%d",a)}function Dc(a){return a}function Ec(a,b,c,d){var e=this._relativeTime[c];return w(e)?e(a,b,c,d):e.replace(/%d/i,a)}function Fc(a,b){var c=this._relativeTime[a>0?"future":"past"];return w(c)?c(b):c.replace(/%s/i,b)}function Gc(a,b,c,d){var e=H(),f=h().set(d,b);return e[c](f,a)}function Hc(a,b,c){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return Gc(a,b,c,"month");var d,e=[];for(d=0;12>d;d++)e[d]=Gc(a,d,c,"month");return e}function Ic(a,b,c,d){"boolean"==typeof a?("number"==typeof b&&(c=b,b=void 0),b=b||""):(b=a,c=b,a=!1,"number"==typeof b&&(c=b,b=void 0),b=b||"");var e=H(),f=a?e._week.dow:0;if(null!=c)return Gc(b,(c+f)%7,d,"day");var g,h=[];for(g=0;7>g;g++)h[g]=Gc(b,(g+f)%7,d,"day");return h}function Jc(a,b){return Hc(a,b,"months")}function Kc(a,b){return Hc(a,b,"monthsShort")}function Lc(a,b,c){return Ic(a,b,c,"weekdays")}function Mc(a,b,c){return Ic(a,b,c,"weekdaysShort")}function Nc(a,b,c){return Ic(a,b,c,"weekdaysMin")}function Oc(){var a=this._data;return this._milliseconds=Le(this._milliseconds),this._days=Le(this._days),this._months=Le(this._months),a.milliseconds=Le(a.milliseconds),a.seconds=Le(a.seconds),a.minutes=Le(a.minutes),a.hours=Le(a.hours),a.months=Le(a.months),a.years=Le(a.years),this}function Pc(a,b,c,d){var e=db(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function Qc(a,b){return Pc(this,a,b,1)}function Rc(a,b){return Pc(this,a,b,-1)}function Sc(a){return 0>a?Math.floor(a):Math.ceil(a)}function Tc(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||0>=f&&0>=g&&0>=h||(f+=864e5*Sc(Vc(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=q(f/1e3),i.seconds=a%60,b=q(a/60),i.minutes=b%60,c=q(b/60),i.hours=c%24,g+=q(c/24),e=q(Uc(g)),h+=e,g-=Sc(Vc(e)),d=q(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function Uc(a){return 4800*a/146097}function Vc(a){return 146097*a/4800}function Wc(a){var b,c,d=this._milliseconds;if(a=K(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+Uc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(Vc(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function Xc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*r(this._months/12)}function Yc(a){return function(){return this.as(a)}}function Zc(a){
+return a=K(a),this[a+"s"]()}function $c(a){return function(){return this._data[a]}}function _c(){return q(this.days()/7)}function ad(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function bd(a,b,c){var d=db(a).abs(),e=_e(d.as("s")),f=_e(d.as("m")),g=_e(d.as("h")),h=_e(d.as("d")),i=_e(d.as("M")),j=_e(d.as("y")),k=e<af.s&&["s",e]||1>=f&&["m"]||f<af.m&&["mm",f]||1>=g&&["h"]||g<af.h&&["hh",g]||1>=h&&["d"]||h<af.d&&["dd",h]||1>=i&&["M"]||i<af.M&&["MM",i]||1>=j&&["y"]||["yy",j];return k[2]=b,k[3]=+a>0,k[4]=c,ad.apply(null,k)}function cd(a,b){return void 0===af[a]?!1:void 0===b?af[a]:(af[a]=b,!0)}function dd(a){var b=this.localeData(),c=bd(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function ed(){var a,b,c,d=bf(this._milliseconds)/1e3,e=bf(this._days),f=bf(this._months);a=q(d/60),b=q(a/60),d%=60,a%=60,c=q(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(0>m?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var fd,gd;gd=Array.prototype.some?Array.prototype.some:function(a){for(var b=Object(this),c=b.length>>>0,d=0;c>d;d++)if(d in b&&a.call(this,b[d],d,b))return!0;return!1};var hd=a.momentProperties=[],id=!1,jd={};a.suppressDeprecationWarnings=!1,a.deprecationHandler=null;var kd;kd=Object.keys?Object.keys:function(a){var b,c=[];for(b in a)f(a,b)&&c.push(b);return c};var ld,md,nd={},od={},pd=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,qd=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,rd={},sd={},td=/\d/,ud=/\d\d/,vd=/\d{3}/,wd=/\d{4}/,xd=/[+-]?\d{6}/,yd=/\d\d?/,zd=/\d\d\d\d?/,Ad=/\d\d\d\d\d\d?/,Bd=/\d{1,3}/,Cd=/\d{1,4}/,Dd=/[+-]?\d{1,6}/,Ed=/\d+/,Fd=/[+-]?\d+/,Gd=/Z|[+-]\d\d:?\d\d/gi,Hd=/Z|[+-]\d\d(?::?\d\d)?/gi,Id=/[+-]?\d+(\.\d{1,3})?/,Jd=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Kd={},Ld={},Md=0,Nd=1,Od=2,Pd=3,Qd=4,Rd=5,Sd=6,Td=7,Ud=8;md=Array.prototype.indexOf?Array.prototype.indexOf:function(a){var b;for(b=0;b<this.length;++b)if(this[b]===a)return b;return-1},R("M",["MM",2],"Mo",function(){return this.month()+1}),R("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),R("MMMM",0,0,function(a){return this.localeData().months(this,a)}),J("month","M"),W("M",yd),W("MM",yd,ud),W("MMM",function(a,b){return b.monthsShortRegex(a)}),W("MMMM",function(a,b){return b.monthsRegex(a)}),$(["M","MM"],function(a,b){b[Nd]=r(a)-1}),$(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[Nd]=e:j(c).invalidMonth=a});var Vd=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/,Wd="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Xd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Yd=Jd,Zd=Jd,$d=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,_d=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,ae=/Z|[+-]\d\d(?::?\d\d)?/,be=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],ce=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],de=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=u("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),R("Y",0,0,function(){var a=this.year();return 9999>=a?""+a:"+"+a}),R(0,["YY",2],0,function(){return this.year()%100}),R(0,["YYYY",4],0,"year"),R(0,["YYYYY",5],0,"year"),R(0,["YYYYYY",6,!0],0,"year"),J("year","y"),W("Y",Fd),W("YY",yd,ud),W("YYYY",Cd,wd),W("YYYYY",Dd,xd),W("YYYYYY",Dd,xd),$(["YYYYY","YYYYYY"],Md),$("YYYY",function(b,c){c[Md]=2===b.length?a.parseTwoDigitYear(b):r(b)}),$("YY",function(b,c){c[Md]=a.parseTwoDigitYear(b)}),$("Y",function(a,b){b[Md]=parseInt(a,10)}),a.parseTwoDigitYear=function(a){return r(a)+(r(a)>68?1900:2e3)};var ee=M("FullYear",!0);a.ISO_8601=function(){};var fe=u("moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Ka.apply(null,arguments);return this.isValid()&&a.isValid()?this>a?this:a:l()}),ge=u("moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Ka.apply(null,arguments);return this.isValid()&&a.isValid()?a>this?this:a:l()}),he=function(){return Date.now?Date.now():+new Date};Qa("Z",":"),Qa("ZZ",""),W("Z",Hd),W("ZZ",Hd),$(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ra(Hd,a)});var ie=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var je=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/,ke=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;db.fn=Oa.prototype;var le=ib(1,"add"),me=ib(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",a.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var ne=u("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});R(0,["gg",2],0,function(){return this.weekYear()%100}),R(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Pb("gggg","weekYear"),Pb("ggggg","weekYear"),Pb("GGGG","isoWeekYear"),Pb("GGGGG","isoWeekYear"),J("weekYear","gg"),J("isoWeekYear","GG"),W("G",Fd),W("g",Fd),W("GG",yd,ud),W("gg",yd,ud),W("GGGG",Cd,wd),W("gggg",Cd,wd),W("GGGGG",Dd,xd),W("ggggg",Dd,xd),_(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=r(a)}),_(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),R("Q",0,"Qo","quarter"),J("quarter","Q"),W("Q",td),$("Q",function(a,b){b[Nd]=3*(r(a)-1)}),R("w",["ww",2],"wo","week"),R("W",["WW",2],"Wo","isoWeek"),J("week","w"),J("isoWeek","W"),W("w",yd),W("ww",yd,ud),W("W",yd),W("WW",yd,ud),_(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=r(a)});var oe={dow:0,doy:6};R("D",["DD",2],"Do","date"),J("date","D"),W("D",yd),W("DD",yd,ud),W("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),$(["D","DD"],Od),$("Do",function(a,b){b[Od]=r(a.match(yd)[0],10)});var pe=M("Date",!0);R("d",0,"do","day"),R("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),R("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),R("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),R("e",0,0,"weekday"),R("E",0,0,"isoWeekday"),J("day","d"),J("weekday","e"),J("isoWeekday","E"),W("d",yd),W("e",yd),W("E",yd),W("dd",function(a,b){return b.weekdaysMinRegex(a)}),W("ddd",function(a,b){return b.weekdaysShortRegex(a)}),W("dddd",function(a,b){return b.weekdaysRegex(a)}),_(["dd","ddd","dddd"],function(a,b,c,d){var e=c._locale.weekdaysParse(a,d,c._strict);null!=e?b.d=e:j(c).invalidWeekday=a}),_(["d","e","E"],function(a,b,c,d){b[d]=r(a)});var qe="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),re="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),se="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),te=Jd,ue=Jd,ve=Jd;R("DDD",["DDDD",3],"DDDo","dayOfYear"),J("dayOfYear","DDD"),W("DDD",Bd),W("DDDD",vd),$(["DDD","DDDD"],function(a,b,c){c._dayOfYear=r(a)}),R("H",["HH",2],0,"hour"),R("h",["hh",2],0,oc),R("k",["kk",2],0,pc),R("hmm",0,0,function(){return""+oc.apply(this)+Q(this.minutes(),2)}),R("hmmss",0,0,function(){return""+oc.apply(this)+Q(this.minutes(),2)+Q(this.seconds(),2)}),R("Hmm",0,0,function(){return""+this.hours()+Q(this.minutes(),2)}),R("Hmmss",0,0,function(){return""+this.hours()+Q(this.minutes(),2)+Q(this.seconds(),2)}),qc("a",!0),qc("A",!1),J("hour","h"),W("a",rc),W("A",rc),W("H",yd),W("h",yd),W("HH",yd,ud),W("hh",yd,ud),W("hmm",zd),W("hmmss",Ad),W("Hmm",zd),W("Hmmss",Ad),$(["H","HH"],Pd),$(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),$(["h","hh"],function(a,b,c){b[Pd]=r(a),j(c).bigHour=!0}),$("hmm",function(a,b,c){var d=a.length-2;b[Pd]=r(a.substr(0,d)),b[Qd]=r(a.substr(d)),j(c).bigHour=!0}),$("hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[Pd]=r(a.substr(0,d)),b[Qd]=r(a.substr(d,2)),b[Rd]=r(a.substr(e)),j(c).bigHour=!0}),$("Hmm",function(a,b,c){var d=a.length-2;b[Pd]=r(a.substr(0,d)),b[Qd]=r(a.substr(d))}),$("Hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[Pd]=r(a.substr(0,d)),b[Qd]=r(a.substr(d,2)),b[Rd]=r(a.substr(e))});var we=/[ap]\.?m?\.?/i,xe=M("Hours",!0);R("m",["mm",2],0,"minute"),J("minute","m"),W("m",yd),W("mm",yd,ud),$(["m","mm"],Qd);var ye=M("Minutes",!1);R("s",["ss",2],0,"second"),J("second","s"),W("s",yd),W("ss",yd,ud),$(["s","ss"],Rd);var ze=M("Seconds",!1);R("S",0,0,function(){return~~(this.millisecond()/100)}),R(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),R(0,["SSS",3],0,"millisecond"),R(0,["SSSS",4],0,function(){return 10*this.millisecond()}),R(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),R(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),R(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),R(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),R(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),J("millisecond","ms"),W("S",Bd,td),W("SS",Bd,ud),W("SSS",Bd,vd);var Ae;for(Ae="SSSS";Ae.length<=9;Ae+="S")W(Ae,Ed);for(Ae="S";Ae.length<=9;Ae+="S")$(Ae,uc);var Be=M("Milliseconds",!1);R("z",0,0,"zoneAbbr"),R("zz",0,0,"zoneName");var Ce=o.prototype;Ce.add=le,Ce.calendar=kb,Ce.clone=lb,Ce.diff=sb,Ce.endOf=Eb,Ce.format=wb,Ce.from=xb,Ce.fromNow=yb,Ce.to=zb,Ce.toNow=Ab,Ce.get=P,Ce.invalidAt=Nb,Ce.isAfter=mb,Ce.isBefore=nb,Ce.isBetween=ob,Ce.isSame=pb,Ce.isSameOrAfter=qb,Ce.isSameOrBefore=rb,Ce.isValid=Lb,Ce.lang=ne,Ce.locale=Bb,Ce.localeData=Cb,Ce.max=ge,Ce.min=fe,Ce.parsingFlags=Mb,Ce.set=P,Ce.startOf=Db,Ce.subtract=me,Ce.toArray=Ib,Ce.toObject=Jb,Ce.toDate=Hb,Ce.toISOString=vb,Ce.toJSON=Kb,Ce.toString=ub,Ce.unix=Gb,Ce.valueOf=Fb,Ce.creationData=Ob,Ce.year=ee,Ce.isLeapYear=ta,Ce.weekYear=Qb,Ce.isoWeekYear=Rb,Ce.quarter=Ce.quarters=Wb,Ce.month=ha,Ce.daysInMonth=ia,Ce.week=Ce.weeks=$b,Ce.isoWeek=Ce.isoWeeks=_b,Ce.weeksInYear=Tb,Ce.isoWeeksInYear=Sb,Ce.date=pe,Ce.day=Ce.days=gc,Ce.weekday=hc,Ce.isoWeekday=ic,Ce.dayOfYear=nc,Ce.hour=Ce.hours=xe,Ce.minute=Ce.minutes=ye,Ce.second=Ce.seconds=ze,Ce.millisecond=Ce.milliseconds=Be,Ce.utcOffset=Ua,Ce.utc=Wa,Ce.local=Xa,Ce.parseZone=Ya,Ce.hasAlignedHourOffset=Za,Ce.isDST=$a,Ce.isDSTShifted=_a,Ce.isLocal=ab,Ce.isUtcOffset=bb,Ce.isUtc=cb,Ce.isUTC=cb,Ce.zoneAbbr=vc,Ce.zoneName=wc,Ce.dates=u("dates accessor is deprecated. Use date instead.",pe),Ce.months=u("months accessor is deprecated. Use month instead",ha),Ce.years=u("years accessor is deprecated. Use year instead",ee),Ce.zone=u("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Va);var De=Ce,Ee={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Fe={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Ge="Invalid date",He="%d",Ie=/\d{1,2}/,Je={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Ke=A.prototype;Ke._calendar=Ee,Ke.calendar=zc,Ke._longDateFormat=Fe,Ke.longDateFormat=Ac,Ke._invalidDate=Ge,Ke.invalidDate=Bc,Ke._ordinal=He,Ke.ordinal=Cc,Ke._ordinalParse=Ie,Ke.preparse=Dc,Ke.postformat=Dc,Ke._relativeTime=Je,Ke.relativeTime=Ec,Ke.pastFuture=Fc,Ke.set=y,Ke.months=ca,Ke._months=Wd,Ke.monthsShort=da,Ke._monthsShort=Xd,Ke.monthsParse=fa,Ke._monthsRegex=Zd,Ke.monthsRegex=ka,Ke._monthsShortRegex=Yd,Ke.monthsShortRegex=ja,Ke.week=Xb,Ke._week=oe,Ke.firstDayOfYear=Zb,Ke.firstDayOfWeek=Yb,Ke.weekdays=bc,Ke._weekdays=qe,Ke.weekdaysMin=dc,Ke._weekdaysMin=se,Ke.weekdaysShort=cc,Ke._weekdaysShort=re,Ke.weekdaysParse=fc,Ke._weekdaysRegex=te,Ke.weekdaysRegex=jc,Ke._weekdaysShortRegex=ue,Ke.weekdaysShortRegex=kc,Ke._weekdaysMinRegex=ve,Ke.weekdaysMinRegex=lc,Ke.isPM=sc,Ke._meridiemParse=we,Ke.meridiem=tc,E("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===r(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=u("moment.lang is deprecated. Use moment.locale instead.",E),a.langData=u("moment.langData is deprecated. Use moment.localeData instead.",H);var Le=Math.abs,Me=Yc("ms"),Ne=Yc("s"),Oe=Yc("m"),Pe=Yc("h"),Qe=Yc("d"),Re=Yc("w"),Se=Yc("M"),Te=Yc("y"),Ue=$c("milliseconds"),Ve=$c("seconds"),We=$c("minutes"),Xe=$c("hours"),Ye=$c("days"),Ze=$c("months"),$e=$c("years"),_e=Math.round,af={s:45,m:45,h:22,d:26,M:11},bf=Math.abs,cf=Oa.prototype;cf.abs=Oc,cf.add=Qc,cf.subtract=Rc,cf.as=Wc,cf.asMilliseconds=Me,cf.asSeconds=Ne,cf.asMinutes=Oe,cf.asHours=Pe,cf.asDays=Qe,cf.asWeeks=Re,cf.asMonths=Se,cf.asYears=Te,cf.valueOf=Xc,cf._bubble=Tc,cf.get=Zc,cf.milliseconds=Ue,cf.seconds=Ve,cf.minutes=We,cf.hours=Xe,cf.days=Ye,cf.weeks=_c,cf.months=Ze,cf.years=$e,cf.humanize=dd,cf.toISOString=ed,cf.toString=ed,cf.toJSON=ed,cf.locale=Bb,cf.localeData=Cb,cf.toIsoString=u("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ed),cf.lang=ne,R("X",0,0,"unix"),R("x",0,0,"valueOf"),W("x",Fd),W("X",Id),$("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),$("x",function(a,b,c){c._d=new Date(r(a))}),a.version="2.13.0",b(Ka),a.fn=De,a.min=Ma,a.max=Na,a.now=he,a.utc=h,a.unix=xc,a.months=Jc,a.isDate=d,a.locale=E,a.invalid=l,a.duration=db,a.isMoment=p,a.weekdays=Lc,a.parseZone=yc,a.localeData=H,a.isDuration=Pa,a.monthsShort=Kc,a.weekdaysMin=Nc,a.defineLocale=F,a.updateLocale=G,a.locales=I,a.weekdaysShort=Mc,a.normalizeUnits=K,a.relativeTimeThreshold=cd,a.prototype=De;var df=a;return df}); \ No newline at end of file
diff --git a/tools/infra-dashboard/js/script.js b/tools/infra-dashboard/js/script.js
new file mode 100644
index 00000000..07c832a2
--- /dev/null
+++ b/tools/infra-dashboard/js/script.js
@@ -0,0 +1,27 @@
+jQuery(document).ready(function () {
+ // If svg is not supported
+ if (!Modernizr.svg) {
+ jQuery('img[src$=".svg"]').each(function() {
+ jQuery(this).attr('src', jQuery(this).attr('src').replace('.svg', '.png'));
+ });
+ }
+ // If media queries are not supported
+ if(!Modernizr.mq('only all')) {
+ jQuery('head').append('<link id="no-mq" rel="stylesheet" type="text/css">');
+ jQuery("link#no-mq").attr("href", "/joomla/media/templates/highsoft_bootstrap/css/ie.css");
+ }
+ // Sidebar click animation
+ jQuery('.nav-sidebar > li').click(function () {
+ if (!jQuery(this).hasClass("active")) {
+ jQuery('.nav-sidebar > li.active > div.active').removeClass('active');
+ jQuery('.nav-sidebar > li.active > ul').slideUp("slow");
+ jQuery('.nav-sidebar > li.active').removeClass('active');
+ jQuery(this).addClass("active");
+ jQuery('.nav-sidebar > li.active > ul').slideDown("slow");
+ jQuery('.nav-sidebar > li.active > div').addClass('active');
+ }
+ });
+ jQuery("#sidebar-toggle").click(function (e) {
+ jQuery("#wrap").toggleClass("toggled");
+ });
+}); \ No newline at end of file
diff --git a/tools/infra-dashboard/js/test_graph.js b/tools/infra-dashboard/js/test_graph.js
new file mode 100644
index 00000000..1d1d5e43
--- /dev/null
+++ b/tools/infra-dashboard/js/test_graph.js
@@ -0,0 +1,108 @@
+$(function () {
+
+ // Get the CSV and create the chart
+ $.getJSON('https://www.highcharts.com/samples/data/jsonp.php?filename=analytics.csv&callback=?', function (csv) {
+
+ $('#container').highcharts({
+
+ data: {
+ csv: csv
+ },
+
+ title: {
+ text: 'Daily visits at www.highcharts.com'
+ },
+
+ subtitle: {
+ text: 'Source: Google Analytics'
+ },
+
+ xAxis: {
+ tickInterval: 7 * 24 * 3600 * 1000, // one week
+ tickWidth: 0,
+ gridLineWidth: 1,
+ labels: {
+ align: 'left',
+ x: 3,
+ y: -3
+ }
+ },
+
+ yAxis: [{ // left y axis
+ title: {
+ text: null
+ },
+ labels: {
+ align: 'left',
+ x: 3,
+ y: 16,
+ format: '{value:.,0f}'
+ },
+ showFirstLabel: false
+ }, { // right y axis
+ linkedTo: 0,
+ gridLineWidth: 0,
+ opposite: true,
+ title: {
+ text: null
+ },
+ labels: {
+ align: 'right',
+ x: -3,
+ y: 16,
+ format: '{value:.,0f}'
+ },
+ showFirstLabel: false
+ }],
+
+ legend: {
+ align: 'left',
+ verticalAlign: 'top',
+ y: 20,
+ floating: true,
+ borderWidth: 0
+ },
+
+ tooltip: {
+ shared: true,
+ crosshairs: true
+ },
+
+ plotOptions: {
+ series: {
+ cursor: 'pointer',
+ point: {
+ events: {
+ click: function (e) {
+ hs.htmlExpand(null, {
+ pageOrigin: {
+ x: e.pageX || e.clientX,
+ y: e.pageY || e.clientY
+ },
+ headingText: this.series.name,
+ maincontentText: Highcharts.dateFormat('%A, %b %e, %Y', this.x) + ':<br/> ' +
+ this.y + ' visits',
+ width: 200
+ });
+ }
+ }
+ },
+ marker: {
+ lineWidth: 1
+ }
+ }
+ },
+
+ series: [{
+ name: 'All visits',
+ lineWidth: 4,
+ marker: {
+ radius: 4
+ }
+ }, {
+ name: 'New visitors'
+ }]
+ });
+ });
+
+});
diff --git a/tools/infra-dashboard/media/ajax-loader.gif b/tools/infra-dashboard/media/ajax-loader.gif
new file mode 100644
index 00000000..3c2f7c05
--- /dev/null
+++ b/tools/infra-dashboard/media/ajax-loader.gif
Binary files differ
diff --git a/tools/infra-dashboard/media/collaborative-projects-logo.png b/tools/infra-dashboard/media/collaborative-projects-logo.png
new file mode 100644
index 00000000..195ad081
--- /dev/null
+++ b/tools/infra-dashboard/media/collaborative-projects-logo.png
Binary files differ
diff --git a/tools/infra-dashboard/media/diagonal-white.png b/tools/infra-dashboard/media/diagonal-white.png
new file mode 100644
index 00000000..9772997c
--- /dev/null
+++ b/tools/infra-dashboard/media/diagonal-white.png
Binary files differ
diff --git a/tools/infra-dashboard/media/favicon_400x400.jpg b/tools/infra-dashboard/media/favicon_400x400.jpg
new file mode 100644
index 00000000..a45d1bf2
--- /dev/null
+++ b/tools/infra-dashboard/media/favicon_400x400.jpg
Binary files differ
diff --git a/tools/infra-dashboard/media/loader.white.gif b/tools/infra-dashboard/media/loader.white.gif
new file mode 100644
index 00000000..f2a1bc0c
--- /dev/null
+++ b/tools/infra-dashboard/media/loader.white.gif
Binary files differ
diff --git a/tools/infra-dashboard/media/opnfvlogo.JPG b/tools/infra-dashboard/media/opnfvlogo.JPG
new file mode 100644
index 00000000..ef3fe355
--- /dev/null
+++ b/tools/infra-dashboard/media/opnfvlogo.JPG
Binary files differ
diff --git a/tools/infra-dashboard/pages/ci_pods.php b/tools/infra-dashboard/pages/ci_pods.php
new file mode 100644
index 00000000..5f51530d
--- /dev/null
+++ b/tools/infra-dashboard/pages/ci_pods.php
@@ -0,0 +1,91 @@
+<script type="text/javascript">
+ $(document).ready(function() {
+ $('#example').DataTable( {
+ "order": [[ 3, "desc" ],[ 2, "desc" ]]
+ } );
+ } );
+</script>
+
+<?php
+ include '../utils/jenkinsAdapter.php';
+ include '../utils/database.php';
+
+ connectDB();
+ $result = mysql_query("SELECT * FROM resource, pod WHERE resource.resource_id=pod.resource_id;");
+ closeDB();
+
+ echo '<table id="example" class="table table-striped table-bordered" cellspacing="0" width="100%">';
+ echo "<thead>";
+ echo "<tr>";
+ echo "<th>Name</th>";
+ echo "<th>Slave Name</th>";
+ echo "<th>Status</th>";
+ echo "<th>Installer</th>";
+ echo "<th>Scenario</th>";
+ echo "<th>Branch</th>";
+ echo "<th>Job</th>";
+ echo "</tr>";
+ echo "</thead>";
+ echo "<tbody>";
+
+ while ($row = mysql_fetch_array($result)) {
+ $slave = $row{'slavename'};
+ if (! isCiPod($slave)) continue;
+
+ $slave_url = getSlaveUrl($slave);
+ $status = getSlaveStatus($slave);
+
+ $job_name = "";
+ $job_installer = "";
+ $job_branch = "";
+ $job_url = "";
+ $job_scenario = "";
+ $job_type = "";
+
+ if ($status == 'online'){
+ $job_params = getJJob($slave);
+ $job_name = $job_params['name'];
+ $job_installer = $job_params['installer'];
+ $job_branch = $job_params['branch'];
+ $job_url = $job_params['url']."lastBuild/consoleFull";
+ $job_scenario = $job_params['scenario'];
+ $job_type = $job_params['type'];
+ }
+
+ echo "<tr>";
+ echo "<th><a target='_blank' href='".$row{'link'}."'>".$row{'name'}."</a></th>";
+ echo "<th><a target='_blank' href='".$slave_url."'>".$slave."</a></th>";
+ if ($status == "online") $color = "#BEFAAA";
+ else $color = "#FAAAAB";
+ echo "<th style='background-color: ".$color.";'>".$status."</th>";
+ if ($job_type == "0") $class = "blink_me";
+ else $class="";
+ echo "<th class='".$class."'>".$job_installer."</th>";
+ echo "<th class='".$class."'>".$job_scenario."</th>";
+ echo "<th class='".$class."'>".$job_branch."</th>";
+ $green = '#33cc00';
+ $grey = '#646F73';
+ $red = '#FF5555';
+ $orange = '#EDD62B';
+ if ($job_type == "0") { // job running
+ echo "<th><a class='blink_me' style='font-size:12px;color:".$grey.";' target='_blank' href='".$job_url."'>".$job_name."</a></th>";
+ }
+ else if ($job_type == "1") {// last job successful
+ echo "<th><a style='font-size:12px;color:".$green.";' target='_blank' href='".$job_url."'>".$job_name."</a></th>";
+ }
+ else if ($job_type == "2") {// last job failed
+ echo "<th><a style='font-size:12px;color:".$red."' target='_blank' href='".$job_url."'>".$job_name."</a></th>";
+ }
+ else if ($job_type == "3") {// last job is unstable
+ echo "<th><a style='font-size:12px;color:".$orange."' target='_blank' href='".$job_url."'>".$job_name."</a></th>";
+ }
+ else {
+ echo "<th><a style='font-size:12px;' target='_blank' href='".$job_url."'>".$job_name."</a></th>";
+ }
+ echo "</tr>";
+ }
+ echo '</tbody>';
+ echo '</table>';
+
+
+?>
diff --git a/tools/infra-dashboard/pages/dev_pods.php b/tools/infra-dashboard/pages/dev_pods.php
new file mode 100644
index 00000000..144504b5
--- /dev/null
+++ b/tools/infra-dashboard/pages/dev_pods.php
@@ -0,0 +1,342 @@
+<?php
+ // trick for the calendar object, if we use always the same id name, it doesn't work properly
+ $random = time();
+ $calendar_id = "calendar_".$random;
+?>
+
+
+<script type="text/javascript">
+ $(document).ready(function() {
+ $('#example').DataTable( {
+ "order": [[ 2, "desc" ],[ 5, "desc"]]
+ } );
+ var date = new Date();
+ var d = date.getDate();
+ var m = date.getMonth();
+ var y = date.getFullYear();
+ } );
+
+ $(function() {
+ var dialog, form,
+
+ // From http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#e-mail-state-%28type=email%29
+ emailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
+ email = $( "#email" ),
+ password = $( "#password" ),
+ allFields = $( [] ).add( email ).add( password ),
+ tips = $( ".validateTips" );
+
+ function updateTips( t ) {
+ tips
+ .text( t )
+ .addClass( "ui-state-highlight" );
+ setTimeout(function() {
+ tips.removeClass( "ui-state-highlight", 1500 );
+ }, 500 );
+ }
+
+ function checkLength( o, n, min, max ) {
+ if ( o.val().length > max || o.val().length < min ) {
+ o.addClass( "ui-state-error" );
+ updateTips( "Length of " + n + " must be between " +
+ min + " and " + max + "." );
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ function checkRegexp( o, regexp, n ) {
+ if ( !( regexp.test( o.val() ) ) ) {
+ o.addClass( "ui-state-error" );
+ updateTips( n );
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ function bookResource() {
+ var user_id = $('#hd_user_id').val();
+ if (user_id == "") {
+ alert ("Only registered users can book.");
+ return;
+ }
+ var resource_id = $('#resource_id').val();
+ var resource_name = $('#resource_name').val();
+ var purpose = $('#purpose').val();
+ var starttime = $('#starttime').val();
+ var endtime = $('#endtime').val();
+ $.ajax({
+ type: 'POST',
+ url: "utils/book.php",
+ data: {action: 'book', resource_id: resource_id, resource_name: resource_name, user_id: user_id, start: starttime, end: endtime, purpose: purpose, start: starttime, end: endtime},
+ success: function(data){
+ if (data == "1") alert("Booking not possible (your account is not associated with a role). Please contact the administrator.");
+ else if (data == "2") alert("You are not allowed to book this resource.");
+ else {
+ alert(data)
+ //location.reload();
+ var href = document.location.protocol +"//"+ document.location.hostname + document.location.pathname
+ location.href = href + "?page=devpods"
+ }
+ },
+ errpr: function(data){
+ alert("error")
+ }
+
+ });
+
+ dialog.dialog( "close" );
+ return true;
+ }
+
+ dialog = $( "#dialog-form" ).dialog({
+ autoOpen: false,
+ height: 800,
+ width: 900,
+ modal: true,
+ resizable:false,
+ buttons: {
+ "Book": bookResource,
+ Cancel: function() {
+ dialog.dialog( "close" );
+ $('#starttime').attr('value', "");
+ $('#endtime').attr('value', "");
+ $('#purpose').attr('value', "");
+ $(".ui-dialog-buttonpane button:contains('Book')").attr("disabled", true)
+ .addClass("ui-state-disabled");
+ }
+ },
+ close: function() {
+ form[ 0 ].reset();
+ allFields.removeClass( "ui-state-error" );
+ }
+ });
+
+ form = dialog.find( "form" ).on( "submit", function( event ) {
+ event.preventDefault();
+ bookResource();
+ });
+
+ $(".ui-dialog-buttonpane button:contains('Book')").attr("disabled", true)
+ .addClass("ui-state-disabled");
+ dialog_event = $( "#dialog_event" ).dialog({
+ autoOpen: false,
+ height: 400,
+ width: 420,
+ modal: true,
+ resizable:false,
+ buttons: {
+ Close: function() {
+ dialog_event.dialog( "close" );
+ },
+ "Release": function() {
+ alert("Not working yet.");
+ }
+ },
+ });
+
+
+ $( ".btn-book" ).button().on( "click", function() {
+ var resource_id = $(this).attr('id');
+ var resource_name = $(this).attr('value');
+ $('#resource_id').attr('value', resource_id);
+ $('#resource_name').attr('value', resource_name);
+ var title = "Book resource: ".concat(resource_name);
+ var calendar_id = '<?=$calendar_id?>';
+ $('#<?=$calendar_id?>').fullCalendar( 'destroy' );
+ $.ajax({
+ type: 'POST',
+ url: "utils/book.php",
+ data: {action: 'getBookedDates', resource_id: resource_id},
+
+ success: function(data){
+ //if (data != "") {
+ var calendarOptions = {
+ height: 660,
+ header: {
+ left: 'prev,next today',
+ center: 'title',
+ right: 'agendaWeek,month'
+ },
+ defaultView: 'agendaWeek',
+ slotDuration: '00:60:00',
+ slotLabelFormat:"HH:mm",
+ firstDay: 1,
+ nowIndicator: true,
+ allDaySlot: false,
+ selectOverlap: false,
+ selectable: true,
+ selectHelper: true,
+ editable: false,
+ timezone: 'UTC',
+ eventStartEditable: false,
+ eventDurationEditable: false,
+ events: jQuery.parseJSON( data ),
+ select: function(start, end) {
+ var view = $('#<?=$calendar_id?>').fullCalendar('getView');
+ if (view.name == "month") return
+ var title = prompt('Purpose of this booking:');
+ var eventData;
+ var provisional_id = "537818F62BC63518ECE15338FB86C8BE";
+ if (title) {
+ $('#<?=$calendar_id?>').fullCalendar('removeEvents',provisional_id);
+ eventData = {
+ id: provisional_id,
+ title: title,
+ start: start,
+ end: end
+ };
+ start=moment(start).format('YYYY-MM-DD HH:mm:ss');
+ end=moment(end).format('YYYY-MM-DD HH:mm:ss');
+ $('#<?=$calendar_id?>').fullCalendar('renderEvent', eventData, true); // stick? = true
+ $('#starttime').attr('value', start);
+ $('#endtime').attr('value', end);
+ $('#purpose').attr('value', title);
+ $(".ui-dialog-buttonpane button:contains('Book')").attr("disabled", false)
+ .removeClass("ui-state-disabled");
+ }
+ $('#<?=$calendar_id?>').fullCalendar('unselect');
+
+ },
+ eventLimit: true, // allow "more" link when too many events
+ timeFormat: 'H(:mm)', // uppercase H for 24-hour clock
+ eventClick: function(event) {
+ $('#dg-start').text(event.start);
+ $('#dg-end').text(event.end);
+ $('#dg-booker-email').text(event.booker_email);
+ $('#dg-purpose').text(event.title);
+ dialog_event.dialog( "open" );
+ }
+ }
+ $('#<?=$calendar_id?>').fullCalendar(calendarOptions);
+ $('#<?=$calendar_id?>').fullCalendar('render');
+ //}
+ //else {
+ // if first time (it has never been booked)
+ // $('#<?=$calendar_id?>').fullCalendar('removeEventSource');
+ //}
+ },
+ error: function(data){
+ alert("error getting booked dates.");
+ }
+
+ });
+ dialog.dialog( 'option', 'title', title);
+ dialog.dialog( "open" );
+ ///$('#<?=$calendar_id?>').fullCalendar( 'rerenderEvents' )
+ });
+ });
+</script>
+
+<style>
+ .fc-divider {
+ display:none !important;
+ }
+</style>
+
+<?php
+
+ include '../utils/jenkinsAdapter.php';
+ include '../utils/database.php';
+
+ connectDB();
+ //$q = "select r.ID,r.NAME,r.LINK,r.DESCR,r.ACTIVE,b.BOOKEDBY,b.BOOKEDFROM,b.BOOKEDUNTIL,b.PURPOSE from resource r left join booking b on r.ID = b.RES_Id and now() between b.BOOKEDFROM and b.BOOKEDUNTIL where r.TYPE=2;";
+ $q = "SELECT r.resource_id,r.name as resname,r.slavename, r.link,u.user_id,u.name as username,u.email,b.starttime,b.endtime,b.purpose from resource r LEFT JOIN pod p LEFT JOIN booking b INNER JOIN user u ON u.user_id = b.user_id ON b.resource_id = p.resource_id AND now() > b.starttime AND now() <= b.endtime ON r.resource_id = p.resource_id;";
+ $result = mysql_query($q);
+
+ //SELECT r.name as resname,r.slavename, r.link, r.resource_id,u.user_id,u.name as username,u.email,b.starttime,b.endtime,b.purpose from resource r LEFT JOIN pod p LEFT JOIN booking b INNER JOIN user u ON u.user_id = b.user_id ON b.resource_id = p.resource_id AND now() > b.starttime AND now() <= b.endtime ON r.resource_id = p.resource_id WHERE type_id=2;
+ closeDB();
+
+ echo '<table id="example" class="table table-striped table-bordered" cellspacing="0" width="100%">';
+ echo "<thead>";
+ echo "<tr>";
+ echo "<th>Name</th>";
+ echo "<th>Slave Name</th>";
+ echo "<th>Booked by</th>";
+ echo "<th>Booked until</th>";
+ echo "<th>Purpose</th>";
+ echo "<th>Status</th>";
+ echo "<th></th>";
+ echo "</tr>";
+ echo "</thead>";
+ echo "<tbody>";
+
+ while ($row = mysql_fetch_array($result)) {
+ $slave = $row{'slavename'};
+ if (! isDevPod($slave)) continue;
+
+ $slave_url = getSlaveUrl($slave);
+ $status = getSlaveStatus($slave);
+ echo "<tr>";
+ echo "<th><a target='_blank' href='".$row{'link'}."'>".$row{'resname'}."</a></th>";
+ echo "<th><a target='_blank' href='".$slave_url."'>".$slave."</a></th>";
+ if ($row{'username'} != "") $booker = $row{'username'};
+ else $booker="-";
+ echo "<th>".$booker."</th>";
+
+ if ($row{'endtime'} != "") {
+ $until = strtotime($row{'endtime'});
+ echo "<th>".date('d/M/Y', $until)."</th>";
+ //$until = $row{'endtime'};
+ } else {
+ $until="-";
+ echo "<th>".$until."</th>";
+ }
+ if ($row{'purpose'} != "") $purpose = $row{'purpose'};
+ else $purpose="-";
+ echo "<th>".$purpose."</th>";
+ $active = 'true';
+ if ($status == "online") $color = "#BEFAAA";
+ else $color = "#FAAAAB";
+ echo "<th style='background-color: ".$color.";'>".$status."</th>";
+
+ echo "<th><button id='".$row{'resource_id'}."' value='".$row{'slavename'}."' class='btn-book' type='button'>Book</button></th>";
+ echo "</tr>";
+ }
+ echo '</tbody>';
+ echo '</table>';
+?>
+
+
+<div id="dialog-form" title="Book resource">
+ <form>
+ <div id='<?=$calendar_id?>' style="margin-top:10px"></div>
+ <input type="submit" tabindex="-1" style="position:absolute; top:-100px"/>
+ </form>
+</div>
+
+
+<div id="dialog_event" title="event">
+ <table>
+ <tr>
+ <td style="width:100px">Booked by:</td>
+ <td><p id="dg-booker-email"></p></td>
+ </tr>
+ <tr>
+ <td>Start:</td>
+ <td><p id="dg-start"></p></td>
+ </tr>
+ <tr>
+ <td>End:</td>
+ <td><p id="dg-end"></p></td>
+ </tr>
+ <tr>
+ <td>Purpose:</td>
+ <td><p id="dg-purpose"></p></td>
+ </tr>
+ <table>
+</div>
+
+
+
+<input type="hidden" id="resource_id" name="resource_id" value="10"/>
+<input type="hidden" id="resource_name" name="resource_name"/>
+<input type="hidden" id="starttime" value=""/>
+<input type="hidden" id="endtime" value=""/>
+<input type="hidden" id="purpose" value=""/>
+<input type="hidden" id="event_id" value=""/>
+
+
+
diff --git a/tools/infra-dashboard/pages/slaves.php b/tools/infra-dashboard/pages/slaves.php
new file mode 100644
index 00000000..4eb2add9
--- /dev/null
+++ b/tools/infra-dashboard/pages/slaves.php
@@ -0,0 +1,60 @@
+<script type="text/javascript">
+ $(document).ready(function() {
+ $('#example').DataTable( {
+ "order": [[ 2, "desc" ], [ 1, "desc" ]]
+ } );
+ } );
+</script>
+
+<?php
+ include '../utils/jenkinsAdapter.php';
+
+ echo '<table id="example" class="table table-striped table-bordered" cellspacing="0" width="100%">';
+ echo "<thead>";
+ echo "<tr>";
+ echo "<th>Slave name</th>";
+ echo "<th>Status</th>";
+ echo "<th>Current build</th>";
+ echo "</tr>";
+ echo "</thead>";
+ echo "<tbody>";
+
+ foreach ($SLAVES->xpath('computer') as &$value) {
+
+ $slave = $value->displayName;
+ $idle = $value->idle;
+ $slave_url = getSlaveUrl($slave);
+ $status = getSlaveStatus($slave);
+
+ if ($status == "online" and $idle == "true") {
+ $status = "online / idle";
+ $color = "#C8D6C3";
+ }
+ else if ($status == "online") $color = "#BEFAAA";
+ else $color = "#FAAAAB";
+
+ $job_name = "";
+ $job_url = "";
+ $job_scenario = "";
+
+
+ if ($status == 'online') {
+ $job_params = getJJob($slave);
+ $job_name = $job_params['name'];
+ $job_url = $job_params['url'];
+ $job_scenario = $job_params['scenario'];
+ }
+
+ echo "<tr>";
+ echo "<th><a target='_blank' href='".$slave_url."'>".$slave."</a></th>";
+
+ echo "<th style='background-color: ".$color.";'>".$status."</th>";
+ echo "<th><a class='blink_me' style='font-size:12px;color:#33cc00;' target='_blank' href='".$job_url."'>".$job_name."</a></th>";
+
+ echo "</tr>";
+ }
+ echo '</tbody>';
+ echo '</table>';
+
+
+?>
diff --git a/tools/infra-dashboard/populateDB.txt b/tools/infra-dashboard/populateDB.txt
new file mode 100644
index 00000000..d3f8f5e4
--- /dev/null
+++ b/tools/infra-dashboard/populateDB.txt
@@ -0,0 +1,203 @@
+
+CREATE DATABASE opnfv_pharos;
+USE opnfv_pharos;
+SHOW TABLES;
+use opnfv_pharos;
+
+DROP TABLE resource;
+CREATE TABLE resource (
+ resource_id INT UNSIGNED AUTO_INCREMENT,
+ name VARCHAR(100) NOT NULL,
+ slavename VARCHAR(50),
+ description VARCHAR(300),
+ link VARCHAR(100),
+ bookable BOOLEAN DEFAULT false,
+ active BOOLEAN DEFAULT true,
+ PRIMARY KEY (resource_id)
+);
+
+DROP TABLE server;
+CREATE TABLE server (
+ server_id INT UNSIGNED AUTO_INCREMENT,
+ resource_id INT NOT NULL,
+ model VARCHAR(200),
+ cpu VARCHAR(200),
+ ram VARCHAR(200),
+ storage VARCHAR(200),
+ count INT DEFAULT 1,
+ PRIMARY KEY (server_id)
+);
+
+DROP TABLE pod;
+CREATE TABLE pod (
+ pod_id INT UNSIGNED AUTO_INCREMENT,
+ resource_id INT NOT NULL,
+ chassis VARCHAR(500),
+ PRIMARY KEY (pod_id)
+);
+
+DROP TABLE user;
+CREATE TABLE user(
+ user_id INT UNSIGNED AUTO_INCREMENT,
+ name VARCHAR(100) NOT NULL,
+ email VARCHAR(100) NOT NULL UNIQUE,
+ password VARCHAR(100) NOT NULL,
+ company VARCHAR(100),
+ creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (user_id)
+);
+
+DROP TABLE role;
+CREATE TABLE role (
+ role_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(30),
+ description VARCHAR(300)
+);
+
+DROP TABLE user_role;
+CREATE TABLE user_role (
+ user_id INT NOT NULL,
+ role_id INT NOT NULL,
+ description VARCHAR(300),
+ PRIMARY KEY (user_id, role_id)
+);
+
+
+DROP TABLE user_resource;
+CREATE TABLE user_resource (
+ user_id INT NOT NULL,
+ resource_id INT NOT NULL,
+ PRIMARY KEY (user_id, resource_id)
+);
+
+
+
+DROP TABLE booking;
+CREATE TABLE booking(
+ booking_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ resource_id INT NOT NULL,
+ user_id INT NOT NULL,
+ starttime DATETIME NOT NULL,
+ endtime DATETIME NOT NULL,
+ creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ purpose VARCHAR(300)
+);
+
+/*
+describe resource;
+describe server;
+describe pod;
+describe pod_type;
+describe user;
+describe role;
+describe user_role;
+describe user_resource;
+describe booking;
+*/
+
+/* POD TYPES */
+INSERT INTO pod_type (name, description) VALUES ("ci_pod", "PODS for CI usage only");
+INSERT INTO pod_type (name, description) VALUES ("dev_pod", "PODS development");
+
+
+/* CI PODS */
+INSERT INTO resource (name, slavename, description, link) VALUES ("Linux Foundation POD 1", "lf-pod1", "Some description", "https://wiki.opnfv.org/display/pharos/Lf+Lab");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='lf-pod1';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Linux Foundation POD 2", "lf-pod2", "Some description", "https://wiki.opnfv.org/display/pharos/Lf+Lab");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='lf-pod2';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Ericsson POD 2", "ericsson-pod2", "Some description", "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='ericsson-pod2';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Intel POD 2", "intel-pod2", "Some description", "https://wiki.opnfv.org/display/pharos/Intel+Pod2");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='intel-pod2';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Intel POD 5", "intel-pod5", "Some description", "https://wiki.opnfv.org/display/pharos/Intel+Pod5");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='intel-pod5';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Intel POD 6", "intel-pod6", "Some description", "https://wiki.opnfv.org/display/pharos/Intel+Pod6");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='intel-pod6';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Intel POD 8", "intel-pod8", "Some description", "https://wiki.opnfv.org/display/pharos/Intel+Pod8");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='intel-pod8';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Huawei POD 1", "huawei-pod1", "Some description", "https://wiki.opnfv.org/display/pharos/Huawei+Hosting");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='huawei-pod1';
+
+
+
+
+
+/* SOME DEV PODS */
+
+
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Intel POD 3", "intel-pod3", "Some description", true, "https://wiki.opnfv.org/display/pharos/Intel+Pod3");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='intel-pod3';
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Dell POD 1", "dell-pod1", "Some description", true, "https://wiki.opnfv.org/display/pharos/Dell+Hosting");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='dell-pod1';
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Dell POD 2", "dell-pod2", "Some description", true, "https://wiki.opnfv.org/display/pharos/Dell+Hosting");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='dell-pod2';
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Orange POD 2", "orange-pod2", "Some description", true, "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='orange-pod2';
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Arm POD 1", "arm-build1", "Some description", true, "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='arm-build1';
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Ericsson POD 1", "ericsson-pod1", "Some description", true, "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='ericsson-pod1';
+
+INSERT INTO resource (name, slavename, description, bookable,link) VALUES ("Huawei POD 2", "huawei-pod2", "Some description", true, "https://wiki.opnfv.org/display/pharos/Huawei+Hosting");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='huawei-pod2';
+
+INSERT INTO resource (name, slavename, description, bookable,link) VALUES ("Huawei POD 3", "huawei-pod3", "Some description", true, "https://wiki.opnfv.org/display/pharos/Huawei+Hosting");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='huawei-pod3';
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Huawei POD 4", "huawei-pod4", "Some description", true, "https://wiki.opnfv.org/display/pharos/Huawei+Hosting");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='huawei-pod4';
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Intel POD 9", "intel-pod9", "Some description", true, "https://wiki.opnfv.org/display/pharos/Intel+Pod9");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='intel-pod9';
+
+
+SELECT * FROM resource;
+SELECT * FROM pod;
+
+
+INSERT INTO role (name, description) VALUES ("admin", "Administrator of the system");
+INSERT INTO role (name, description) VALUES ("lab_owner", "Owner of a lab.");
+INSERT INTO role (name, description) VALUES ("troubleshooter", "A person who can book a pod for troubleshooting.");
+SELECT * FROM role;
+
+INSERT INTO user (name, password, email, company) VALUES ("Jose Lausuch", md5("opnfv"), "jose.lausuch@ericsson.com", "Ericsson");
+INSERT INTO user (name, password, email, company) VALUES ("Daniel Smith", md5("opnfv"), "daniel.smith@ericsson.com", "Ericsson");
+INSERT INTO user (name, password, email, company) VALUES ("Jack Morgan", md5("opnfv"), "jack.morgan@intel.com", "Intel");
+INSERT INTO user (name, password, email, company) VALUES ("Fatih Degirmenci", md5("opnfv"), "fatih.degirmenci@ericsson.com", "Ericsson");
+INSERT INTO user (name, password, email, company) VALUES ("Trevor Cooper", md5("opnfv"), "trevor.cooper@intel.com", "Intel");
+SELECT * FROM user;
+
+INSERT INTO user_role (user_id, role_id) SELECT user_id,role_id FROM user,role WHERE email="jose.lausuch@ericsson.com" AND role.name="admin";
+INSERT INTO user_role (user_id, role_id) SELECT user_id,role_id FROM user,role WHERE email="daniel.smith@ericsson.com" AND role.name="lab_owner";
+INSERT INTO user_role (user_id, role_id) SELECT user_id,role_id FROM user,role WHERE email="jack.morgan@intel.com" AND role.name="lab_owner";
+INSERT INTO user_role (user_id, role_id) SELECT user_id,role_id FROM user,role WHERE email="jack.morgan@intel.com" AND role.name="troubleshooter";
+INSERT INTO user_role (user_id, role_id) SELECT user_id,role_id FROM user,role WHERE email="fatih.degirmenci@ericsson.com" AND role.name="troubleshooter";
+INSERT INTO user_role (user_id, role_id) SELECT user_id,role_id FROM user,role WHERE email="trevor.cooper@intel.com" AND role.name="troubleshooter";
+SELECT * FROM user_role;
+
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="daniel.smith@ericsson.com" and slavename="ericsson-pod1";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="daniel.smith@ericsson.com" and slavename="ericsson-pod2";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="jack.morgan@intel.com" and slavename="intel-pod2";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="jack.morgan@intel.com" and slavename="intel-pod3";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="jack.morgan@intel.com" and slavename="intel-pod5";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="jack.morgan@intel.com" and slavename="intel-pod6";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="jack.morgan@intel.com" and slavename="intel-pod8";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="jack.morgan@intel.com" and slavename="intel-pod9";
+SELECT * FROM user_resource;
+
+
+
+
diff --git a/tools/infra-dashboard/utils/book.php b/tools/infra-dashboard/utils/book.php
new file mode 100644
index 00000000..6d4c5b20
--- /dev/null
+++ b/tools/infra-dashboard/utils/book.php
@@ -0,0 +1,82 @@
+<?php
+
+ include 'database.php';
+
+ function book() {
+ $resource_id = $_POST['resource_id'];
+ $resource_name = $_POST['resource_name'];
+ $user_id = $_POST['user_id'];
+ $start = $_POST['start'];
+ $end = $_POST['end'];
+ $purpose = $_POST['purpose'];
+
+ $query = "select role.name as rolename from user, role, user_role where user.user_id = ".$user_id." and role.role_id=user_role.role_id and user_role.user_id=user.user_id;";
+ $result = mysql_query($query);
+
+ if(mysql_num_rows($result) == 0) {
+ echo "1"; //return a code instead of a meesage. Display the message later in javascript according to the returned code.
+ //echo "Booking not possible (your account is not associated with a role). Please contact the administrator.";
+ exit;
+ }
+ $is_only_lab_owner = true;
+ while ($row = mysql_fetch_array($result)) {
+ $rolename = $row['rolename'];
+ if ($rolename != "lab_owner") $is_only_lab_owner = false;
+ }
+ if ($is_only_lab_owner) {
+ $query = "select * from user u inner join user_resource r on r.user_id=u.user_id and u.user_id=".$user_id." and r.resource_id=".$resource_id.";";
+ $result = mysql_query($query);
+ if(mysql_num_rows($result) == 0) {
+ echo "2";
+ //echo "You are not allowed to book this resource. ";
+ exit;
+ }
+ }
+ $query = "INSERT INTO booking (resource_id, user_id, starttime, endtime, purpose) VALUES (".$resource_id.",".$user_id.",'".$start."','".$end."', '".$purpose."');";
+ $result = mysql_query($query);
+ if(mysql_insert_id()>0){
+ echo "Booking successful. The resource '".$resource_name."' is booked from ".$start." to ".$end.".";
+ }
+ else{
+ echo "Mysql Error : ".mysql_error().". Query = ".$query;
+ }
+
+ }
+
+ function getBookedDates() {
+ $resource_id = $_POST['resource_id'];
+ $query = "SELECT b.booking_id, b.resource_id,u.name as username,u.email,b.starttime,b.endtime,b.creation,b.purpose FROM booking as b,user as u WHERE b.resource_id=".$resource_id." AND b.user_id=u.user_id;";
+ $result = mysql_query($query);
+
+ $events = array();
+ while ($row = mysql_fetch_array($result)) {
+ $e = array();
+ $e['id'] = $row['booking_id'];
+ $e['booker_name'] = $row['username'];
+ $e['booker_email'] = $row['email'];
+ $e['title'] = $row['purpose'];
+ $e['start'] = $row['starttime'];
+ $e['end'] = $row['endtime'];
+ $e['bookdate'] = $row['creation'];
+
+ // Merge the event array into the return array
+ array_push($events, $e);
+ }
+
+ echo json_encode($events);
+ }
+
+
+
+ $action = $_POST['action'];
+
+ connectDB();
+ if ($action == "book") {
+ book();
+ } elseif ($action == "getBookedDates" ) {
+ getBookedDates();
+ } else {
+ echo "Invalid POST action.";
+ }
+ closeDB();
+?>
diff --git a/tools/infra-dashboard/utils/database.php b/tools/infra-dashboard/utils/database.php
new file mode 100644
index 00000000..bacf3637
--- /dev/null
+++ b/tools/infra-dashboard/utils/database.php
@@ -0,0 +1,18 @@
+<?php
+
+ date_default_timezone_set('UTC');
+ function connectDB() {
+ $username = "root";
+ $password = "opnfv";
+ $hostname = "localhost";
+ $dbhandle = mysql_connect($hostname, $username, $password)
+ or die("Unable to connect to MySQL.");
+ $selected = mysql_select_db("opnfv_pharos",$dbhandle)
+ or die("Could not select opnfv_pharos DB.");
+ }
+
+ function closeDB(){
+ mysql_connect($dbhandle);
+ }
+
+?>
diff --git a/tools/infra-dashboard/utils/jenkinsAdapter.php b/tools/infra-dashboard/utils/jenkinsAdapter.php
new file mode 100644
index 00000000..c238feb7
--- /dev/null
+++ b/tools/infra-dashboard/utils/jenkinsAdapter.php
@@ -0,0 +1,204 @@
+<?php
+
+ function getSlaves() {
+ $query="https://build.opnfv.org/ci/computer/api/xml?tree=computer[displayName,offline,idle]";
+ $output = file_get_contents($query);
+ $xml = simplexml_load_string($output);
+ if ($xml) return $xml;
+ else return "";
+ }
+
+ function getCiSlaves(){
+ $query="https://build.opnfv.org/ci/label/ci-pod/api/xml?xpath=labelAtom/node[nodeName]&wrapper=nodes";
+ $output = file_get_contents($query);
+ $xml = simplexml_load_string($output);
+ if ($xml) return $xml;
+ else return "";
+ }
+
+ function getAllBuilds(){
+ $query="https://build.opnfv.org/ci/api/xml?tree=jobs[displayName,url,lastBuild[fullDisplayName,building,builtOn,timestamp,result]]";
+ $output = file_get_contents($query);
+ $xml = simplexml_load_string($output);
+ if ($xml) return $xml;
+ else return "";
+ }
+
+ $SLAVES = getSlaves();
+ $CI_PODS = getCiSlaves();
+ $ALL_BUILDS = getAllBuilds();
+
+ function getActiveBuilds() {
+ global $ALL_BUILDS;
+ //$query="https://build.opnfv.org/ci/api/xml?tree=jobs[displayName,url,lastBuild[fullDisplayName,building,builtOn,timestamp]]&xpath=hudson/job[lastBuild/building=%27true%27]&wrapper=hudson";
+ $xml = $ALL_BUILDS->xpath('job[lastBuild/building="true"]');
+ if ($xml) return $xml;
+ else return "";
+ }
+
+ $ACTIVE_BUILDS = getActiveBuilds();
+
+ function slaveExists($slave) {
+ global $SLAVES;
+ $slave = $SLAVES->xpath('computer[displayName="'.$slave.'"]');
+ if ($slave) return true;
+ else return false;
+ }
+
+ function getSlaveStatus($slave) {
+ global $SLAVES;
+ $status = "unknown";
+ if (!slaveExists($slave)) return $status;
+ $slave = $SLAVES->xpath('computer[displayName="'.$slave.'"]');
+ $offline = $slave[0]->offline;
+
+ if ($offline == "true") $status = "offline";
+ else $status = "online";
+ return $status;
+ }
+
+ function getSlaveUrl($slave) {
+ if (slaveExists($slave)) return "https://build.opnfv.org/ci/computer/".$slave;
+ else return "";
+ }
+
+
+ function isCiPod($slave) {
+ global $CI_PODS;
+ $result = $CI_PODS->xpath('node[nodeName="'.$slave.'"]');
+ if ($result) return true;
+ else return false;
+ }
+
+ function isDevPod($slave) {
+ global $CI_PODS;
+ if (isCiPod($slave)) return false;
+ else if (strpos($slave, 'pod') !== false) return true;
+ else return false;
+ }
+
+ function parseJobString($str) {
+ $scenario = '';
+ $installer = '';
+ $branch = '';
+ $installers = array("fuel", "joid", "apex", "compass");
+ $branches = array("master","arno", "brahmaputra", "colorado");
+ $arr = split ('[ -]', $str);
+ for($x = 0; $x < count($arr); $x++) {
+ if (strcmp($arr[$x],"os") == 0) //all the scenarios start with 'os'
+ $scenario = $arr[$x].'-'.$arr[$x+1].'-'.$arr[$x+2].'-'.$arr[$x+3];
+ else if (in_array($arr[$x], $installers))
+ $installer = $arr[$x];
+ else if (in_array($arr[$x], $branches))
+ $branch = $arr[$x];
+ }
+ $arr2 = explode(' ', $str);
+ $jobname = $arr2[0]; //take first word as job name
+
+ return array(
+ "jobname"=>$jobname,
+ "installer"=>$installer,
+ "branch"=>$branch,
+ "scenario"=>$scenario
+ );
+ }
+
+
+ function getJJob($slave) {
+ global $ALL_BUILDS;
+ if (!slaveExists($slave)) return "";
+
+ //$builds = $ALL_BUILDS;
+ //$xml = $ALL_BUILDS->xpath('job[lastBuild/building="true"][lastBuild/builtOn="'.$slave.'"]');
+ $builds = $ALL_BUILDS->xpath('job[lastBuild/builtOn="'.$slave.'"]');
+ if (! $builds) { //the slave does not have jobs in building state
+ //echo "NO JOBS FOUND";
+ return "";
+ }
+ else {
+ //is there any active build?
+ $builds = $ALL_BUILDS->xpath('job[lastBuild/building="true"][lastBuild/builtOn="'.$slave.'"]');
+ if ($builds) { // there are active builds for this slave
+ //print_r($builds);
+
+ $child_job = simplexml_import_dom($builds[0]);
+ foreach ($builds as &$build) {
+ $int1 = intval($build->lastBuild->timestamp);
+ $int2 = intval($child_job->lastBuild->timestamp);
+ if ($int1 > $int2) {
+ $child_job = simplexml_import_dom($build);
+ }
+ }
+ $url = strval($child_job->url);
+ $fullDisplayName = $child_job->lastBuild->fullDisplayName;
+ //echo $fullDisplayName."<br>";
+
+ $params = parseJobString($fullDisplayName);
+
+ $type = 0; // type=0 means the job is running
+ $job_params = array(
+ "name"=>$params['jobname'],
+ "url"=>$url,
+ "scenario"=>$params['scenario'],
+ "installer"=>$params['installer'],
+ "branch"=>$params['branch'],
+ "type"=>$type
+ );
+ //print_r($job_params);
+ return $job_params;
+
+ }
+ else { // there are NO active builds for this slave, we take the latest build
+ //echo "NO Active builds";
+ $builds = $ALL_BUILDS->xpath('job[lastBuild/building="false"][lastBuild/builtOn="'.$slave.'"]');
+ $last_job = simplexml_import_dom($builds[0]);
+ //print_r($last_job);
+ foreach ($builds as &$build) {
+ $int1 = intval($build->lastBuild->timestamp);
+ $int2 = intval($last_job->lastBuild->timestamp);
+ if ($int1 > $int2) {
+ $last_job = simplexml_import_dom($build);
+ }
+ }
+ $url = strval($last_job->url);
+ $result = strval($last_job->lastBuild->result);
+ $fullDisplayName = $last_job->lastBuild->fullDisplayName;
+
+ $params = parseJobString($fullDisplayName);
+
+ $type = 3;
+ if ($result == "SUCCESS") $type = 1; // type=1 means it's the last job and it succeded
+ if ($result == "FAILURE") $type = 2; // type=2 means it's the last job and it failed
+ if ($result == "UNSTABLE") $type = 3; // type=3 means it's the last job is unstable
+
+ $job_params = array(
+ "name"=>$params['jobname'],
+ "url"=>$url,
+ "scenario"=>$params['scenario'],
+ "installer"=>$params['installer'],
+ "branch"=>$params['branch'],
+ "type"=>$type
+ );
+
+ return $job_params;
+ //print_r($job_params);
+ }
+
+ }
+ }
+
+ /*
+ $slave = "lf-pod2";
+ $job_params = getJJob($slave);
+
+ $status = getSlaveStatus($slave);
+ echo "Status slave ".$slave.": ".$status."<br>";
+ echo "Job: ".$job_params['name']."<br>";
+ echo "URL: ".$job_params['url']."<br>";
+ echo "Scenario: ".$job_params['scenario']."<br>";
+ echo "Installer: ".$job_params['installer']."<br>";
+ echo "Branch: ".$job_params['branch']."<br>";
+ echo "Type: ".$job_params['type']."<br>";
+ */
+
+?>
diff --git a/tools/infra-dashboard/utils/login.php b/tools/infra-dashboard/utils/login.php
new file mode 100644
index 00000000..2ac7101d
--- /dev/null
+++ b/tools/infra-dashboard/utils/login.php
@@ -0,0 +1,56 @@
+<?php
+
+ include 'database.php';
+
+
+ function login(){
+ $email = $_POST['email'];
+ $password = $_POST['password'];
+
+ $query = "SELECT * FROM user where EMAIL='".$email."';";
+ $result = mysql_query($query);
+
+ $user = array();
+ if(mysql_num_rows($result) > 0) {
+ $query = "SELECT * FROM user where email='".$email."' and password='".$password."';";
+ $result = mysql_query($query);
+ if(mysql_num_rows($result) > 0) {
+ while($row = mysql_fetch_assoc($result)) {
+ $user = $row;
+ $user["result"] = 0;
+
+ $_SESSION['user_id'] = $user['user_id'];
+ $_SESSION['user_name'] = $user['name'];
+ $_SESSION['user_email'] = $user['email'];
+ }
+ } else {
+ $user["result"] = 1; //wrong password
+ }
+ } else {
+ $user["result"] = 2; //user not registered
+ }
+ echo json_encode($user);
+
+ }
+
+
+ $action = $_POST['action'];
+
+ connectDB();
+ session_start();
+
+ if ($action == "login") {
+ login();
+ } else if ($action == "logout") {
+ unset($_SESSION['user_id']);
+ unset($_SESSION['user_name']);
+ unset($_SESSION['user_email']);
+ session_destroy();
+ } else {
+ echo "Invalid POST action.";
+ }
+ closeDB();
+
+?>
+
+