diff options
Diffstat (limited to 'module/web')
| -rw-r--r-- | module/web/static/css/default/common.less | 1 | ||||
| -rw-r--r-- | module/web/static/css/default/dashboard.less | 51 | ||||
| -rw-r--r-- | module/web/static/css/default/style.less | 32 | ||||
| -rw-r--r-- | module/web/static/css/fontawesome.css | 23 | ||||
| -rw-r--r-- | module/web/static/css/select2.css | 589 | ||||
| -rw-r--r-- | module/web/static/fonts/fontawesome-webfont.eot | bin | 10228 -> 14957 bytes | |||
| -rw-r--r-- | module/web/static/fonts/fontawesome-webfont.ttf | bin | 3724 -> 7440 bytes | |||
| -rw-r--r-- | module/web/static/fonts/fontawesome-webfont.woff | bin | 2504 -> 4404 bytes | |||
| -rw-r--r-- | module/web/static/fonts/fontawesome.txt | 23 | ||||
| -rw-r--r-- | module/web/static/js/config.js | 4 | ||||
| -rw-r--r-- | module/web/static/js/libs/select2-3.2.js | 2566 | ||||
| -rw-r--r-- | module/web/templates/default/base.html | 34 | ||||
| -rw-r--r-- | module/web/templates/default/dashboard.html | 206 | ||||
| -rw-r--r-- | module/web/templates/default/settings.html | 4 | 
14 files changed, 3413 insertions, 120 deletions
| diff --git a/module/web/static/css/default/common.less b/module/web/static/css/default/common.less index 990d665c1..0d46e2a5b 100644 --- a/module/web/static/css/default/common.less +++ b/module/web/static/css/default/common.less @@ -28,6 +28,7 @@  @yellow: #ffd856;  @yellowLighter: lighten(spin(@yellow, 10), 20%);  @yellowLightest: lighten(spin(@yellow, 15), 30%); +@yellowDark: darken(@yellow, 10%);  @blue: #3571a2;  @blueLight: lighten(spin(@blue, 5), 10%); diff --git a/module/web/static/css/default/dashboard.less b/module/web/static/css/default/dashboard.less index fd0ebf61e..7f504ebdf 100644 --- a/module/web/static/css/default/dashboard.less +++ b/module/web/static/css/default/dashboard.less @@ -9,6 +9,57 @@    list-style: none;
  }
 +.sidebar-header {
 +  font-size: 25px;
 +  line-height: 25px;
 +}
 +
 +/*
 +  Packages
 +*/
 +
 +.package-list {
 +  list-style: none;
 +  margin-left: 0;
 +}
 +
 +.package-item {
 +  cursor: pointer;
 +  margin-bottom: 4px;
 +  position: relative;
 +  overflow: hidden;
 +
 +  i {
 +    cursor: move;
 +  }
 +
 +  .progress {
 +    height: 4px;
 +    border-radius: 2px;
 +    margin-bottom: 0;
 +    background-image: none;
 +    background-color: @yellow;
 +    clear: both;
 +  }
 +
 +  .bar-info {
 +    background-image: none;
 +    background-color: @blue;
 +  }
 +
 +}
 +
 +.package-info {
 +  font-size: small;
 +}
 +
 +.package-indicator {
 +//  position: absolute;
 +//  top: 1px;
 +//  right: 0;
 +  float: right;
 +}
 +
  /*
    Package View
  */
 diff --git a/module/web/static/css/default/style.less b/module/web/static/css/default/style.less index 16eaea6b6..4e6f6bd39 100644 --- a/module/web/static/css/default/style.less +++ b/module/web/static/css/default/style.less @@ -44,6 +44,12 @@ a:hover {    padding-bottom: @footer-height;
  }
 +#content-container:before {
 +  display: block;
 +  content: " ";
 +  height: @header-height;
 +}
 +
  /*
    Additional Responsive Class for larger desktop
  */
 @@ -301,13 +307,11 @@ header .logo {    color: @blue;
  }
 -#actionbar-container:before {
 -  display: block;
 -  content: " ";
 -  height: @header-height;
 -}
 +.actionbar {
 +  padding-bottom: 3px;
 +  margin-bottom: 0;
 +  border-bottom: 1px dashed @grey;
 -#actionbar-container .row-fluid {
    height: @actionbar-height;
    padding-top: 2px;
 @@ -315,17 +319,11 @@ header .logo {  }
 -#actionbar {
 -  padding-bottom: 3px;
 -  margin-bottom: 0;
 -  border-bottom: 1px dashed @grey;
 -}
 -
 -#actionbar > li > a {
 +.actionbar > li > a {
    margin-top: 4px;
  }
 -#actionbar .breadcrumb {
 +.actionbar .breadcrumb {
    margin: 0;
    padding-top: 10px;
    padding-bottom: 0;
 @@ -336,17 +334,17 @@ header .logo {  }
 -#actionbar form {
 +.actionbar form {
    margin-top: 8px;
    margin-bottom: 0;
  }
 -#actionbar input, #actionbar button {
 +.actionbar input, .actionbar button {
    padding-top: 2px;
    padding-bottom: 2px;
  }
 -#actionbar .dropdown-menu i {
 +.actionbar .dropdown-menu i {
    margin-top: 4px;
    padding-right: 5px;
  }
 diff --git a/module/web/static/css/fontawesome.css b/module/web/static/css/fontawesome.css index c6dde169e..8ca8bbfbb 100644 --- a/module/web/static/css/fontawesome.css +++ b/module/web/static/css/fontawesome.css @@ -268,12 +268,33 @@ ul.icons li [class*=" iconf-"] {    .btn .iconf-spin.iconf-large {      height: .75em;    } -}.iconf-user:before		{ content: "\f007"; } +}.iconf-search:before		{ content: "\f002"; } +.iconf-user:before		{ content: "\f007"; } +.iconf-ok:before		{ content: "\f00c"; } +.iconf-remove:before		{ content: "\f00d"; } +.iconf-cog:before		{ content: "\f013"; } +.iconf-trash:before		{ content: "\f014"; } +.iconf-list-alt:before		{ content: "\f022"; } +.iconf-tag:before		{ content: "\f02b"; } +.iconf-tags:before		{ content: "\f02c"; } +.iconf-list:before		{ content: "\f03a"; } +.iconf-check:before		{ content: "\f046"; } +.iconf-check-empty:before		{ content: "\f096"; } +.iconf-filter:before		{ content: "\f0b0"; }  .iconf-plus-sign:before		{ content: "\f055"; } +.iconf-chevron-up:before		{ content: "\f077"; } +.iconf-chevron-down:before		{ content: "\f078"; }  .iconf-key:before		{ content: "\f084"; }  .iconf-inbox:before		{ content: "\f01c"; }  .iconf-share:before		{ content: "\f045"; }  .iconf-hdd:before		{ content: "\f0a0"; }  .iconf-group:before		{ content: "\f0c0"; }  .iconf-cloud:before		{ content: "\f0c2"; } +.iconf-carret-left:before		{ content: "\f0d9"; } +.iconf-sort-down:before		{ content: "\f0dd"; } +.iconf-sort-up:before		{ content: "\f0de"; }  .iconf-sitemap:before		{ content: "\f0e8"; } +.iconf-folder-open:before		{ content: "\f07c"; } +.iconf-folder-open-alt:before		{ content: "\f115"; } +.iconf-folder-close:before		{ content: "\f07b"; } +.iconf-folder-close-alt:before		{ content: "\f114"; } diff --git a/module/web/static/css/select2.css b/module/web/static/css/select2.css new file mode 100644 index 000000000..d8b43d876 --- /dev/null +++ b/module/web/static/css/select2.css @@ -0,0 +1,589 @@ +/* +Version: @@ver@@ Timestamp: @@timestamp@@ +*/ +.select2-container { +    position: relative; +    display: inline-block; +    /* inline-block for ie7 */ +    zoom: 1; +    *display: inline; +    vertical-align: top; +} + +.select2-container, +.select2-drop, +.select2-search, +.select2-search input{ +  /* +    Force border-box so that % widths fit the parent +    container without overlap because of margin/padding. + +    More Info : http://www.quirksmode.org/css/box.html +  */ +  -webkit-box-sizing: border-box; /* webkit */ +   -khtml-box-sizing: border-box; /* konqueror */ +     -moz-box-sizing: border-box; /* firefox */ +      -ms-box-sizing: border-box; /* ie */ +          box-sizing: border-box; /* css3 */ +} + +.select2-container .select2-choice { +    display: block; +    height: 26px; +    padding: 0 0 0 8px; +    overflow: hidden; +    position: relative; + +    border: 1px solid #aaa; +    white-space: nowrap; +    line-height: 26px; +    color: #444; +    text-decoration: none; + +    -webkit-border-radius: 4px; +       -moz-border-radius: 4px; +            border-radius: 4px; + +    -webkit-background-clip: padding-box; +       -moz-background-clip: padding; +            background-clip: padding-box; + +    background-color: #fff; +    background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, white)); +    background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 50%); +    background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 50%); +    background-image: -o-linear-gradient(bottom, #eeeeee 0%, #ffffff 50%); +    background-image: -ms-linear-gradient(top, #ffffff 0%, #eeeeee 50%); +    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0); +    background-image: linear-gradient(top, #ffffff 0%, #eeeeee 50%); +} + +.select2-container.select2-drop-above .select2-choice { +    border-bottom-color: #aaa; + +    -webkit-border-radius:0 0 4px 4px; +       -moz-border-radius:0 0 4px 4px; +            border-radius:0 0 4px 4px; + +    background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.9, white)); +    background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 90%); +    background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 90%); +    background-image: -o-linear-gradient(bottom, #eeeeee 0%, white 90%); +    background-image: -ms-linear-gradient(top, #eeeeee 0%,#ffffff 90%); +    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 ); +    background-image: linear-gradient(top, #eeeeee 0%,#ffffff 90%); +} + +.select2-container .select2-choice span { +    margin-right: 26px; +    display: block; +    overflow: hidden; + +    white-space: nowrap; + +    -ms-text-overflow: ellipsis; +     -o-text-overflow: ellipsis; +        text-overflow: ellipsis; +} + +.select2-container .select2-choice abbr { +    display: block; +    width: 12px; +    height: 12px; +    position: absolute; +    right: 26px; +    top: 8px; + +    font-size: 1px; +    text-decoration: none; + +    border: 0; +    background: url('select2.png') right top no-repeat; +    cursor: pointer; +    outline: 0; +} +.select2-container .select2-choice abbr:hover { +    background-position: right -11px; +    cursor: pointer; +} + +.select2-drop-mask { +    position: absolute; +    left: 0; +    top: 0; +    z-index: 9998; +    opacity: 0; +} + +.select2-drop { +    width: 100%; +    margin-top:-1px; +    position: absolute; +    z-index: 9999; +    top: 100%; + +    background: #fff; +    color: #000; +    border: 1px solid #aaa; +    border-top: 0; + +    -webkit-border-radius: 0 0 4px 4px; +       -moz-border-radius: 0 0 4px 4px; +            border-radius: 0 0 4px 4px; + +    -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); +       -moz-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); +         -o-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); +            box-shadow: 0 4px 5px rgba(0, 0, 0, .15); +} + +.select2-drop.select2-drop-above { +    margin-top: 1px; +    border-top: 1px solid #aaa; +    border-bottom: 0; + +    -webkit-border-radius: 4px 4px 0 0; +       -moz-border-radius: 4px 4px 0 0; +            border-radius: 4px 4px 0 0; + +    -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); +       -moz-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); +         -o-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); +            box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); +} + +.select2-container .select2-choice div { +    display: block; +    width: 18px; +    height: 100%; +    position: absolute; +    right: 0; +    top: 0; + +    border-left: 1px solid #aaa; +    -webkit-border-radius: 0 4px 4px 0; +       -moz-border-radius: 0 4px 4px 0; +            border-radius: 0 4px 4px 0; + +    -webkit-background-clip: padding-box; +       -moz-background-clip: padding; +            background-clip: padding-box; + +    background: #ccc; +    background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee)); +    background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%); +    background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%); +    background-image: -o-linear-gradient(bottom, #ccc 0%, #eee 60%); +    background-image: -ms-linear-gradient(top, #cccccc 0%, #eeeeee 60%); +    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0); +    background-image: linear-gradient(top, #cccccc 0%, #eeeeee 60%); +} + +.select2-container .select2-choice div b { +    display: block; +    width: 100%; +    height: 100%; +    background: url('select2.png') no-repeat 0 1px; +} + +.select2-search { +    display: inline-block; +    width: 100%; +    min-height: 26px; +    margin: 0; +    padding-left: 4px; +    padding-right: 4px; + +    position: relative; +    z-index: 10000; + +    white-space: nowrap; +} + +.select2-search-hidden { +    display: block; +    position: absolute; +    left: -10000px; +} + +.select2-search input { +    width: 100%; +    height: auto !important; +    min-height: 26px; +    padding: 4px 20px 4px 5px; +    margin: 0; + +    outline: 0; +    font-family: sans-serif; +    font-size: 1em; + +    border: 1px solid #aaa; +    -webkit-border-radius: 0; +       -moz-border-radius: 0; +            border-radius: 0; + +    -webkit-box-shadow: none; +       -moz-box-shadow: none; +            box-shadow: none; + +    background: #fff url('select2.png') no-repeat 100% -22px; +    background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee)); +    background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%); +    background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%); +    background: url('select2.png') no-repeat 100% -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%); +    background: url('select2.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%); +    background: url('select2.png') no-repeat 100% -22px, linear-gradient(top, #ffffff 85%, #eeeeee 99%); +} + +.select2-drop.select2-drop-above .select2-search input { +    margin-top: 4px; +} + +.select2-search input.select2-active { +    background: #fff url('select2-spinner.gif') no-repeat 100%; +    background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee)); +    background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%); +    background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%); +    background: url('select2-spinner.gif') no-repeat 100%, -o-linear-gradient(bottom, white 85%, #eeeeee 99%); +    background: url('select2-spinner.gif') no-repeat 100%, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%); +    background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(top, #ffffff 85%, #eeeeee 99%); +} + +.select2-container-active .select2-choice, +.select2-container-active .select2-choices { +    border: 1px solid #5897fb; +    outline: none; + +    -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3); +       -moz-box-shadow: 0 0 5px rgba(0,0,0,.3); +         -o-box-shadow: 0 0 5px rgba(0,0,0,.3); +            box-shadow: 0 0 5px rgba(0,0,0,.3); +} + +.select2-dropdown-open .select2-choice { +    border: 1px solid #aaa; +    border-bottom-color: transparent; +    -webkit-box-shadow: 0 1px 0 #fff inset; +       -moz-box-shadow: 0 1px 0 #fff inset; +         -o-box-shadow: 0 1px 0 #fff inset; +            box-shadow: 0 1px 0 #fff inset; + +    -webkit-border-bottom-left-radius: 0; +        -moz-border-radius-bottomleft: 0; +            border-bottom-left-radius: 0; + +    -webkit-border-bottom-right-radius: 0; +        -moz-border-radius-bottomright: 0; +            border-bottom-right-radius: 0; + +    background-color: #eee; +    background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, white), color-stop(0.5, #eeeeee)); +    background-image: -webkit-linear-gradient(center bottom, white 0%, #eeeeee 50%); +    background-image: -moz-linear-gradient(center bottom, white 0%, #eeeeee 50%); +    background-image: -o-linear-gradient(bottom, white 0%, #eeeeee 50%); +    background-image: -ms-linear-gradient(top, #ffffff 0%,#eeeeee 50%); +    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 ); +    background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%); +} + +.select2-dropdown-open .select2-choice div { +    background: transparent; +    border-left: none; +    filter: none; +} +.select2-dropdown-open .select2-choice div b { +    background-position: -18px 1px; +} + +/* results */ +.select2-results { +    max-height: 200px; +    padding: 0 0 0 4px; +    margin: 4px 4px 4px 0; +    position: relative; +    overflow-x: hidden; +    overflow-y: auto; +} + +.select2-results ul.select2-result-sub { +    margin: 0; +} + +.select2-results ul.select2-result-sub > li .select2-result-label { padding-left: 20px } +.select2-results ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 40px } +.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 60px } +.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 80px } +.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 100px } +.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 110px } +.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 120px } + +.select2-results li { +    list-style: none; +    display: list-item; +    background-image: none; +} + +.select2-results li.select2-result-with-children > .select2-result-label { +    font-weight: bold; +} + +.select2-results .select2-result-label { +    padding: 3px 7px 4px; +    margin: 0; +    cursor: pointer; +} + +.select2-results .select2-highlighted { +    background: #3875d7; +    color: #fff; +} + +.select2-results li em { +    background: #feffde; +    font-style: normal; +} + +.select2-results .select2-highlighted em { +    background: transparent; +} + +.select2-results .select2-highlighted ul { +    background: white; +    color: #000; +} + + +.select2-results .select2-no-results, +.select2-results .select2-searching, +.select2-results .select2-selection-limit { +    background: #f4f4f4; +    display: list-item; +} + +/* +disabled look for disabled choices in the results dropdown +*/ +.select2-results .select2-disabled.select2-highlighted { +    color: #666; +    background: #f4f4f4; +    display: list-item; +    cursor: default; +} +.select2-results .select2-disabled { +  background: #f4f4f4; +  display: list-item; +  cursor: default; +} + +.select2-results .select2-selected { +    display: none; +} + +.select2-more-results.select2-active { +    background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%; +} + +.select2-more-results { +    background: #f4f4f4; +    display: list-item; +} + +/* disabled styles */ + +.select2-container.select2-container-disabled .select2-choice { +    background-color: #f4f4f4; +    background-image: none; +    border: 1px solid #ddd; +    cursor: default; +} + +.select2-container.select2-container-disabled .select2-choice div { +    background-color: #f4f4f4; +    background-image: none; +    border-left: 0; +} + +.select2-container.select2-container-disabled .select2-choice abbr { +    display: none +} + + +/* multiselect */ + +.select2-container-multi .select2-choices { +    height: auto !important; +    height: 1%; +    margin: 0; +    padding: 0; +    position: relative; + +    border: 1px solid #aaa; +    cursor: text; +    overflow: hidden; + +    background-color: #fff; +    background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); +    background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); +    background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); +    background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); +    background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%); +    background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%); +} + +.select2-locked { +  padding: 3px 5px 3px 5px !important; +} + +.select2-container-multi .select2-choices { +    min-height: 26px; +} + +.select2-container-multi.select2-container-active .select2-choices { +    border: 1px solid #5897fb; +    outline: none; + +    -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3); +       -moz-box-shadow: 0 0 5px rgba(0,0,0,.3); +         -o-box-shadow: 0 0 5px rgba(0,0,0,.3); +            box-shadow: 0 0 5px rgba(0,0,0,.3); +} +.select2-container-multi .select2-choices li { +    float: left; +    list-style: none; +} +.select2-container-multi .select2-choices .select2-search-field { +    margin: 0; +    padding: 0; +    white-space: nowrap; +} + +.select2-container-multi .select2-choices .select2-search-field input { +    height: 15px; +    padding: 5px; +    margin: 1px 0; + +    font-family: sans-serif; +    font-size: 100%; +    color: #666; +    outline: 0; +    border: 0; +    -webkit-box-shadow: none; +       -moz-box-shadow: none; +         -o-box-shadow: none; +            box-shadow: none; +    background: transparent !important; +} + +.select2-container-multi .select2-choices .select2-search-field input.select2-active { +    background: #fff url('select2-spinner.gif') no-repeat 100% !important; +} + +.select2-default { +    color: #999 !important; +} + +.select2-container-multi .select2-choices .select2-search-choice { +    padding: 3px 5px 3px 18px; +    margin: 3px 0 3px 5px; +    position: relative; + +    line-height: 13px; +    color: #333; +    cursor: default; +    border: 1px solid #aaaaaa; + +    -webkit-border-radius: 3px; +       -moz-border-radius: 3px; +            border-radius: 3px; + +    -webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); +       -moz-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); +            box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); + +    -webkit-background-clip: padding-box; +       -moz-background-clip: padding; +            background-clip: padding-box; + +    background-color: #e4e4e4; +    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0 ); +    background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee)); +    background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); +    background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); +    background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); +    background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); +    background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); +} +.select2-container-multi .select2-choices .select2-search-choice span { +    cursor: default; +} +.select2-container-multi .select2-choices .select2-search-choice-focus { +    background: #d4d4d4; +} + +.select2-search-choice-close { +    display: block; +    width: 12px; +    height: 13px; +    position: absolute; +    right: 3px; +    top: 4px; + +    font-size: 1px; +    outline: none; +    background: url('select2.png') right top no-repeat; +} + +.select2-container-multi .select2-search-choice-close { +    left: 3px; +} + +.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover { +  background-position: right -11px; +} +.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close { +    background-position: right -11px; +} + +/* disabled styles */ +.select2-container-multi.select2-container-disabled .select2-choices{ +    background-color: #f4f4f4; +    background-image: none; +    border: 1px solid #ddd; +    cursor: default; +} + +.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice { +    padding: 3px 5px 3px 5px; +    border: 1px solid #ddd; +    background-image: none; +    background-color: #f4f4f4; +} + +.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { +    display: none; +} +/* end multiselect */ + + +.select2-result-selectable .select2-match, +.select2-result-unselectable .select2-match { +    text-decoration: underline; +} + +.select2-offscreen { +    position: absolute; +    left: -10000px; +} + +/* Retina-ize icons */ + +@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi)  { +  .select2-search input, .select2-search-choice-close, .select2-container .select2-choice abbr, .select2-container .select2-choice div b { +      background-image: url('select2x2.png') !important; +      background-repeat: no-repeat !important; +      background-size: 60px 40px !important; +  } +  .select2-search input { +      background-position: 100% -21px !important; +  } +} diff --git a/module/web/static/fonts/fontawesome-webfont.eot b/module/web/static/fonts/fontawesome-webfont.eotBinary files differ index 2321edaca..4bdee495f 100644 --- a/module/web/static/fonts/fontawesome-webfont.eot +++ b/module/web/static/fonts/fontawesome-webfont.eot diff --git a/module/web/static/fonts/fontawesome-webfont.ttf b/module/web/static/fonts/fontawesome-webfont.ttfBinary files differ index eb8c0caaa..b98525105 100644 --- a/module/web/static/fonts/fontawesome-webfont.ttf +++ b/module/web/static/fonts/fontawesome-webfont.ttf diff --git a/module/web/static/fonts/fontawesome-webfont.woff b/module/web/static/fonts/fontawesome-webfont.woffBinary files differ index 442371fd3..9d753b3e1 100644 --- a/module/web/static/fonts/fontawesome-webfont.woff +++ b/module/web/static/fonts/fontawesome-webfont.woff diff --git a/module/web/static/fonts/fontawesome.txt b/module/web/static/fonts/fontawesome.txt index ac89f6e74..ee36a3130 100644 --- a/module/web/static/fonts/fontawesome.txt +++ b/module/web/static/fonts/fontawesome.txt @@ -1,11 +1,32 @@  # List of icons needed for font-awesome  # name, uni from http://icnfnt.com/ +search 002  user 007 +ok 00c +remove 00d +cog 013 +trash 014 +list-alt 022 +tag 02b +tags 02c +list 03a +check 046 +check-empty 096 +filter 0b0  plus-sign 055 +chevron-up 077 +chevron-down 078  key 084  inbox 01c  share 045  hdd 0a0  group 0c0  cloud 0c2 -sitemap 0e8
\ No newline at end of file +carret-left 0d9 +sort-down 0dd +sort-up 0de +sitemap 0e8 +folder-open 07c +folder-open-alt 115 +folder-close 07b +folder-close-alt 114
\ No newline at end of file diff --git a/module/web/static/js/config.js b/module/web/static/js/config.js index 40039c592..0c0eac3fc 100644 --- a/module/web/static/js/config.js +++ b/module/web/static/js/config.js @@ -10,6 +10,7 @@ require.config({          transit: "libs/jquery.transit-0.9.9",          animate: "libs/jquery.animate-enhanced-0.99",          omniwindow: "libs/jquery.omniwindow", +        select2: "libs/select2-3.2",          bootstrap: "libs/bootstrap-2.2.2",          underscore: "libs/lodash-1.0.rc3", @@ -33,7 +34,8 @@ require.config({          "flot": ["jquery"],          "transit": ["jquery"],          "omniwindow": ["jquery"], +        "select2": ["jquery"],          "bootstrap": ["jquery"], -        "animate": ["jquery"], +        "animate": ["jquery"]      } // end Shim Configuration  });
\ No newline at end of file diff --git a/module/web/static/js/libs/select2-3.2.js b/module/web/static/js/libs/select2-3.2.js new file mode 100644 index 000000000..d41f090d7 --- /dev/null +++ b/module/web/static/js/libs/select2-3.2.js @@ -0,0 +1,2566 @@ +/* +Copyright 2012 Igor Vaynberg + +Version: @@ver@@ Timestamp: @@timestamp@@ + +This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU +General Public License version 2 (the "GPL License"). You may choose either license to govern your +use of this software only upon the condition that you accept all of the terms of either the Apache +License or the GPL License. + +You may obtain a copy of the Apache License and the GPL License at: + +    http://www.apache.org/licenses/LICENSE-2.0 +    http://www.gnu.org/licenses/gpl-2.0.html + +Unless required by applicable law or agreed to in writing, software distributed under the +Apache License or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the Apache License and the GPL License for +the specific language governing permissions and limitations under the Apache License and the GPL License. +*/ + (function ($) { + 	if(typeof $.fn.each2 == "undefined"){ + 		$.fn.extend({ + 			/* +			* 4-10 times faster .each replacement +			* use it carefully, as it overrides jQuery context of element on each iteration +			*/ +			each2 : function (c) { +				var j = $([0]), i = -1, l = this.length; +				while ( +					++i < l +					&& (j.context = j[0] = this[i]) +					&& c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object +				); +				return this; +			} + 		}); + 	} +})(jQuery); + +(function ($, undefined) { +    "use strict"; +    /*global document, window, jQuery, console */ + +    if (window.Select2 !== undefined) { +        return; +    } + +    var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer, +        lastMousePosition, $document; + +    KEY = { +        TAB: 9, +        ENTER: 13, +        ESC: 27, +        SPACE: 32, +        LEFT: 37, +        UP: 38, +        RIGHT: 39, +        DOWN: 40, +        SHIFT: 16, +        CTRL: 17, +        ALT: 18, +        PAGE_UP: 33, +        PAGE_DOWN: 34, +        HOME: 36, +        END: 35, +        BACKSPACE: 8, +        DELETE: 46, +        isArrow: function (k) { +            k = k.which ? k.which : k; +            switch (k) { +            case KEY.LEFT: +            case KEY.RIGHT: +            case KEY.UP: +            case KEY.DOWN: +                return true; +            } +            return false; +        }, +        isControl: function (e) { +            var k = e.which; +            switch (k) { +            case KEY.SHIFT: +            case KEY.CTRL: +            case KEY.ALT: +                return true; +            } + +            if (e.metaKey) return true; + +            return false; +        }, +        isFunctionKey: function (k) { +            k = k.which ? k.which : k; +            return k >= 112 && k <= 123; +        } +    }; + +    $document = $(document); + +    nextUid=(function() { var counter=1; return function() { return counter++; }; }()); + +    function indexOf(value, array) { +        var i = 0, l = array.length; +        for (; i < l; i = i + 1) if (value === array[i]) return i; +        return -1; +    } + +    /** +     * Compares equality of a and b +     * @param a +     * @param b +     */ +    function equal(a, b) { +        return a===b; +    } + +    /** +     * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty +     * strings +     * @param string +     * @param separator +     */ +    function splitVal(string, separator) { +        var val, i, l; +        if (string === null || string.length < 1) return []; +        val = string.split(separator); +        for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]); +        return val; +    } + +    function getSideBorderPadding(element) { +        return element.outerWidth(false) - element.width(); +    } + +    function installKeyUpChangeEvent(element) { +        var key="keyup-change-value"; +        element.bind("keydown", function () { +            if ($.data(element, key) === undefined) { +                $.data(element, key, element.val()); +            } +        }); +        element.bind("keyup", function () { +            var val= $.data(element, key); +            if (val !== undefined && element.val() !== val) { +                $.removeData(element, key); +                element.trigger("keyup-change"); +            } +        }); +    } + +    $document.bind("mousemove", function (e) { +        lastMousePosition = {x: e.pageX, y: e.pageY}; +    }); + +    /** +     * filters mouse events so an event is fired only if the mouse moved. +     * +     * filters out mouse events that occur when mouse is stationary but +     * the elements under the pointer are scrolled. +     */ +    function installFilteredMouseMove(element) { +	    element.bind("mousemove", function (e) { +            var lastpos = lastMousePosition; +            if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) { +                $(e.target).trigger("mousemove-filtered", e); +            } +        }); +    } + +    /** +     * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made +     * within the last quietMillis milliseconds. +     * +     * @param quietMillis number of milliseconds to wait before invoking fn +     * @param fn function to be debounced +     * @param ctx object to be used as this reference within fn +     * @return debounced version of fn +     */ +    function debounce(quietMillis, fn, ctx) { +        ctx = ctx || undefined; +        var timeout; +        return function () { +            var args = arguments; +            window.clearTimeout(timeout); +            timeout = window.setTimeout(function() { +                fn.apply(ctx, args); +            }, quietMillis); +        }; +    } + +    /** +     * A simple implementation of a thunk +     * @param formula function used to lazily initialize the thunk +     * @return {Function} +     */ +    function thunk(formula) { +        var evaluated = false, +            value; +        return function() { +            if (evaluated === false) { value = formula(); evaluated = true; } +            return value; +        }; +    }; + +    function installDebouncedScroll(threshold, element) { +        var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);}); +        element.bind("scroll", function (e) { +            if (indexOf(e.target, element.get()) >= 0) notify(e); +        }); +    } + +    function killEvent(event) { +        event.preventDefault(); +        event.stopPropagation(); +    } +    function killEventImmediately(event) { +        event.preventDefault(); +        event.stopImmediatePropagation(); +    } + +    function measureTextWidth(e) { +        if (!sizer){ +        	var style = e[0].currentStyle || window.getComputedStyle(e[0], null); +        	sizer = $(document.createElement("div")).css({ +	            position: "absolute", +	            left: "-10000px", +	            top: "-10000px", +	            display: "none", +	            fontSize: style.fontSize, +	            fontFamily: style.fontFamily, +	            fontStyle: style.fontStyle, +	            fontWeight: style.fontWeight, +	            letterSpacing: style.letterSpacing, +	            textTransform: style.textTransform, +	            whiteSpace: "nowrap" +	        }); +            sizer.attr("class","select2-sizer"); +        	$("body").append(sizer); +        } +        sizer.text(e.val()); +        return sizer.width(); +    } + +    function markMatch(text, term, markup, escapeMarkup) { +        var match=text.toUpperCase().indexOf(term.toUpperCase()), +            tl=term.length; + +        if (match<0) { +            markup.push(escapeMarkup(text)); +            return; +        } + +        markup.push(escapeMarkup(text.substring(0, match))); +        markup.push("<span class='select2-match'>"); +        markup.push(escapeMarkup(text.substring(match, match + tl))); +        markup.push("</span>"); +        markup.push(escapeMarkup(text.substring(match + tl, text.length))); +    } + +    /** +     * Produces an ajax-based query function +     * +     * @param options object containing configuration paramters +     * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax +     * @param options.url url for the data +     * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url. +     * @param options.dataType request data type: ajax, jsonp, other datatatypes supported by jQuery's $.ajax function or the transport function if specified +     * @param options.traditional a boolean flag that should be true if you wish to use the traditional style of param serialization for the ajax request +     * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often +     * @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Select2. +     *      The expected format is an object containing the following keys: +     *      results array of objects that will be used as choices +     *      more (optional) boolean indicating whether there are more results available +     *      Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true} +     */ +    function ajax(options) { +        var timeout, // current scheduled but not yet executed request +            requestSequence = 0, // sequence used to drop out-of-order responses +            handler = null, +            quietMillis = options.quietMillis || 100; + +        return function (query) { +            window.clearTimeout(timeout); +            timeout = window.setTimeout(function () { +                requestSequence += 1; // increment the sequence +                var requestNumber = requestSequence, // this request's sequence number +                    data = options.data, // ajax data function +                    url = options.url, // ajax url string or function +                    transport = options.transport || $.ajax, +                    traditional = options.traditional || false, +                    type = options.type || 'GET'; // set type of request (GET or POST) + +                data = data ? data.call(this, query.term, query.page, query.context) : null; +                url = (typeof url === 'function') ? url.call(this, query.term, query.page, query.context) : url; + +                if( null !== handler) { handler.abort(); } + +                handler = transport.call(null, { +                    url: url, +                    dataType: options.dataType, +                    data: data, +                    type: type, +                    traditional: traditional, +                    success: function (data) { +                        if (requestNumber < requestSequence) { +                            return; +                        } +                        // TODO 3.0 - replace query.page with query so users have access to term, page, etc. +                        var results = options.results(data, query.page); +                        query.callback(results); +                    } +                }); +            }, quietMillis); +        }; +    } + +    /** +     * Produces a query function that works with a local array +     * +     * @param options object containing configuration parameters. The options parameter can either be an array or an +     * object. +     * +     * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys. +     * +     * If the object form is used ti is assumed that it contains 'data' and 'text' keys. The 'data' key should contain +     * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text' +     * key can either be a String in which case it is expected that each element in the 'data' array has a key with the +     * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract +     * the text. +     */ +    function local(options) { +        var data = options, // data elements +            dataText, +            text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search + +        if (!$.isArray(data)) { +            text = data.text; +            // if text is not a function we assume it to be a key name +            if (!$.isFunction(text)) { +              dataText = data.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available +              text = function (item) { return item[dataText]; }; +            } +            data = data.results; +        } + +        return function (query) { +            var t = query.term, filtered = { results: [] }, process; +            if (t === "") { +                query.callback({results: data}); +                return; +            } + +            process = function(datum, collection) { +                var group, attr; +                datum = datum[0]; +                if (datum.children) { +                    group = {}; +                    for (attr in datum) { +                        if (datum.hasOwnProperty(attr)) group[attr]=datum[attr]; +                    } +                    group.children=[]; +                    $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); }); +                    if (group.children.length || query.matcher(t, text(group), datum)) { +                        collection.push(group); +                    } +                } else { +                    if (query.matcher(t, text(datum), datum)) { +                        collection.push(datum); +                    } +                } +            }; + +            $(data).each2(function(i, datum) { process(datum, filtered.results); }); +            query.callback(filtered); +        }; +    } + +    // TODO javadoc +    function tags(data) { +        var isFunc = $.isFunction(data); +        return function (query) { +            var t = query.term, filtered = {results: []}; +            $(isFunc ? data() : data).each(function () { +                var isObject = this.text !== undefined, +                    text = isObject ? this.text : this; +                if (t === "" || query.matcher(t, text)) { +                    filtered.results.push(isObject ? this : {id: this, text: this}); +                } +            }); +            query.callback(filtered); +        }; +    } + +    /** +     * Checks if the formatter function should be used. +     * +     * Throws an error if it is not a function. Returns true if it should be used, +     * false if no formatting should be performed. +     * +     * @param formatter +     */ +    function checkFormatter(formatter, formatterName) { +        if ($.isFunction(formatter)) return true; +        if (!formatter) return false; +        throw new Error("formatterName must be a function or a falsy value"); +    } + +    function evaluate(val) { +        return $.isFunction(val) ? val() : val; +    } + +    function countResults(results) { +        var count = 0; +        $.each(results, function(i, item) { +            if (item.children) { +                count += countResults(item.children); +            } else { +                count++; +            } +        }); +        return count; +    } + +    /** +     * Default tokenizer. This function uses breaks the input on substring match of any string from the +     * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those +     * two options have to be defined in order for the tokenizer to work. +     * +     * @param input text user has typed so far or pasted into the search field +     * @param selection currently selected choices +     * @param selectCallback function(choice) callback tho add the choice to selection +     * @param opts select2's opts +     * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value +     */ +    function defaultTokenizer(input, selection, selectCallback, opts) { +        var original = input, // store the original so we can compare and know if we need to tell the search to update its text +            dupe = false, // check for whether a token we extracted represents a duplicate selected choice +            token, // token +            index, // position at which the separator was found +            i, l, // looping variables +            separator; // the matched separator + +        if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined; + +        while (true) { +            index = -1; + +            for (i = 0, l = opts.tokenSeparators.length; i < l; i++) { +                separator = opts.tokenSeparators[i]; +                index = input.indexOf(separator); +                if (index >= 0) break; +            } + +            if (index < 0) break; // did not find any token separator in the input string, bail + +            token = input.substring(0, index); +            input = input.substring(index + separator.length); + +            if (token.length > 0) { +                token = opts.createSearchChoice(token, selection); +                if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) { +                    dupe = false; +                    for (i = 0, l = selection.length; i < l; i++) { +                        if (equal(opts.id(token), opts.id(selection[i]))) { +                            dupe = true; break; +                        } +                    } + +                    if (!dupe) selectCallback(token); +                } +            } +        } + +        if (original!==input) return input; +    } + +    /** +     * Creates a new class +     * +     * @param superClass +     * @param methods +     */ +    function clazz(SuperClass, methods) { +        var constructor = function () {}; +        constructor.prototype = new SuperClass; +        constructor.prototype.constructor = constructor; +        constructor.prototype.parent = SuperClass.prototype; +        constructor.prototype = $.extend(constructor.prototype, methods); +        return constructor; +    } + +    AbstractSelect2 = clazz(Object, { + +        // abstract +        bind: function (func) { +            var self = this; +            return function () { +                func.apply(self, arguments); +            }; +        }, + +        // abstract +        init: function (opts) { +            var results, search, resultsSelector = ".select2-results", mask; + +            // prepare options +            this.opts = opts = this.prepareOpts(opts); + +            this.id=opts.id; + +            // destroy if called on an existing component +            if (opts.element.data("select2") !== undefined && +                opts.element.data("select2") !== null) { +                this.destroy(); +            } + +            this.enabled=true; +            this.container = this.createContainer(); + +            this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid()); +            this.containerSelector="#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1'); +            this.container.attr("id", this.containerId); + +            // cache the body so future lookups are cheap +            this.body = thunk(function() { return opts.element.closest("body"); }); + +            // create the dropdown mask if doesnt already exist +            mask = $("#select2-drop-mask"); +            if (mask.length == 0) { +                mask = $(document.createElement("div")); +                mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask"); +                mask.hide(); +                mask.appendTo(this.body()); +                mask.bind("mousedown touchstart", function (e) { +                    var dropdown = $("#select2-drop"), self; +                    if (dropdown.length > 0) { +                        self=dropdown.data("select2"); +                        if (self.opts.selectOnBlur) { +                            self.selectHighlighted({noFocus: true}); +                        } +                        self.close(); +                    } +                }); +            } + +            if (opts.element.attr("class") !== undefined) { +                this.container.addClass(opts.element.attr("class").replace(/validate\[[\S ]+] ?/, '')); +            } + +            this.container.css(evaluate(opts.containerCss)); +            this.container.addClass(evaluate(opts.containerCssClass)); + +            this.elementTabIndex = this.opts.element.attr("tabIndex"); + +            // swap container for the element +            this.opts.element +                .data("select2", this) +                .addClass("select2-offscreen") +                .bind("focus.select2", function() { $(this).select2("focus")}) +                .attr("tabIndex", "-1") +                .before(this.container); +            this.container.data("select2", this); + +            this.dropdown = this.container.find(".select2-drop"); +            this.dropdown.addClass(evaluate(opts.dropdownCssClass)); +            this.dropdown.data("select2", this); + +            this.results = results = this.container.find(resultsSelector); +            this.search = search = this.container.find("input.select2-input"); + +            search.attr("tabIndex", this.elementTabIndex); + +            this.resultsPage = 0; +            this.context = null; + +            // initialize the container +            this.initContainer(); +            this.initContainerWidth(); + +            installFilteredMouseMove(this.results); +            this.dropdown.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent)); + +            installDebouncedScroll(80, this.results); +            this.dropdown.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded)); + +            // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel +            if ($.fn.mousewheel) { +                results.mousewheel(function (e, delta, deltaX, deltaY) { +                    var top = results.scrollTop(), height; +                    if (deltaY > 0 && top - deltaY <= 0) { +                        results.scrollTop(0); +                        killEvent(e); +                    } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) { +                        results.scrollTop(results.get(0).scrollHeight - results.height()); +                        killEvent(e); +                    } +                }); +            } + +            installKeyUpChangeEvent(search); +            search.bind("keyup-change", this.bind(this.updateResults)); +            search.bind("focus", function () { search.addClass("select2-focused"); if (search.val() === " ") search.val(""); }); +            search.bind("blur", function () { search.removeClass("select2-focused");}); + +            this.dropdown.delegate(resultsSelector, "mouseup", this.bind(function (e) { +                if ($(e.target).closest(".select2-result-selectable").length > 0) { +                    this.highlightUnderEvent(e); +                    this.selectHighlighted(e); +                } else { +                    this.focusSearch(); +                } +                killEvent(e); +            })); + +            // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening +            // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's +            // dom it will trigger the popup close, which is not what we want +            this.dropdown.bind("click mouseup mousedown", function (e) { e.stopPropagation(); }); + +            if ($.isFunction(this.opts.initSelection)) { +                // initialize selection based on the current value of the source element +                this.initSelection(); + +                // if the user has provided a function that can set selection based on the value of the source element +                // we monitor the change event on the element and trigger it, allowing for two way synchronization +                this.monitorSource(); +            } + +            if (opts.element.is(":disabled") || opts.element.is("[readonly='readonly']")) this.disable(); +        }, + +        // abstract +        destroy: function () { +            var select2 = this.opts.element.data("select2"); + +            if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; } + +            if (select2 !== undefined) { + +                select2.container.remove(); +                select2.dropdown.remove(); +                select2.opts.element +                    .removeData("select2") +                    .unbind(".select2") +                    .attr("tabIndex", this.elementTabIndex) +                    .show(); +            } +        }, + +        // abstract +        prepareOpts: function (opts) { +            var element, select, idKey, ajaxUrl; + +            element = opts.element; + +            if (element.get(0).tagName.toLowerCase() === "select") { +                this.select = select = opts.element; +            } + +            if (select) { +                // these options are not allowed when attached to a select because they are picked up off the element itself +                $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () { +                    if (this in opts) { +                        throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element."); +                    } +                }); +            } + +            opts = $.extend({}, { +                populateResults: function(container, results, query) { +                    var populate,  data, result, children, id=this.opts.id, self=this; + +                    populate=function(results, container, depth) { + +                        var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted; + +                        results = opts.sortResults(results, container, query); + +                        for (i = 0, l = results.length; i < l; i = i + 1) { + +                            result=results[i]; + +                            disabled = (result.disabled === true); +                            selectable = (!disabled) && (id(result) !== undefined); + +                            compound=result.children && result.children.length > 0; + +                            node=$("<li></li>"); +                            node.addClass("select2-results-dept-"+depth); +                            node.addClass("select2-result"); +                            node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable"); +                            if (disabled) { node.addClass("select2-disabled"); } +                            if (compound) { node.addClass("select2-result-with-children"); } +                            node.addClass(self.opts.formatResultCssClass(result)); + +                            label=$(document.createElement("div")); +                            label.addClass("select2-result-label"); + +                            formatted=opts.formatResult(result, label, query); +                            if (formatted!==undefined) { +                                label.html(formatted); +                            } + +                            node.append(label); + +                            if (compound) { + +                                innerContainer=$("<ul></ul>"); +                                innerContainer.addClass("select2-result-sub"); +                                populate(result.children, innerContainer, depth+1); +                                node.append(innerContainer); +                            } + +                            node.data("select2-data", result); +                            container.append(node); +                        } +                    }; + +                    populate(results, container, 0); +                } +            }, $.fn.select2.defaults, opts); + +            if (typeof(opts.id) !== "function") { +                idKey = opts.id; +                opts.id = function (e) { return e[idKey]; }; +            } + +            if ($.isArray(opts.element.data("select2Tags"))) { +                if ("tags" in opts) { +                    throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id"); +                } +                opts.tags=opts.element.attr("data-select2-tags"); +            } + +            if (select) { +                opts.query = this.bind(function (query) { +                    var data = { results: [], more: false }, +                        term = query.term, +                        children, firstChild, process; + +                    process=function(element, collection) { +                        var group; +                        if (element.is("option")) { +                            if (query.matcher(term, element.text(), element)) { +                                collection.push({id:element.attr("value"), text:element.text(), element: element.get(), css: element.attr("class"), disabled: equal(element.attr("disabled"), "disabled") }); +                            } +                        } else if (element.is("optgroup")) { +                            group={text:element.attr("label"), children:[], element: element.get(), css: element.attr("class")}; +                            element.children().each2(function(i, elm) { process(elm, group.children); }); +                            if (group.children.length>0) { +                                collection.push(group); +                            } +                        } +                    }; + +                    children=element.children(); + +                    // ignore the placeholder option if there is one +                    if (this.getPlaceholder() !== undefined && children.length > 0) { +                        firstChild = children[0]; +                        if ($(firstChild).text() === "") { +                            children=children.not(firstChild); +                        } +                    } + +                    children.each2(function(i, elm) { process(elm, data.results); }); + +                    query.callback(data); +                }); +                // this is needed because inside val() we construct choices from options and there id is hardcoded +                opts.id=function(e) { return e.id; }; +                opts.formatResultCssClass = function(data) { return data.css; } +            } else { +                if (!("query" in opts)) { + +                    if ("ajax" in opts) { +                        ajaxUrl = opts.element.data("ajax-url"); +                        if (ajaxUrl && ajaxUrl.length > 0) { +                            opts.ajax.url = ajaxUrl; +                        } +                        opts.query = ajax(opts.ajax); +                    } else if ("data" in opts) { +                        opts.query = local(opts.data); +                    } else if ("tags" in opts) { +                        opts.query = tags(opts.tags); +                        if (opts.createSearchChoice === undefined) { +                            opts.createSearchChoice = function (term) { return {id: term, text: term}; }; +                        } +                        opts.initSelection = function (element, callback) { +                            var data = []; +                            $(splitVal(element.val(), opts.separator)).each(function () { +                                var id = this, text = this, tags=opts.tags; +                                if ($.isFunction(tags)) tags=tags(); +                                $(tags).each(function() { if (equal(this.id, id)) { text = this.text; return false; } }); +                                data.push({id: id, text: text}); +                            }); + +                            callback(data); +                        }; +                    } +                } +            } +            if (typeof(opts.query) !== "function") { +                throw "query function not defined for Select2 " + opts.element.attr("id"); +            } + +            return opts; +        }, + +        /** +         * Monitor the original element for changes and update select2 accordingly +         */ +        // abstract +        monitorSource: function () { +            var el = this.opts.element, sync; + +            el.bind("change.select2", this.bind(function (e) { +                if (this.opts.element.data("select2-change-triggered") !== true) { +                    this.initSelection(); +                } +            })); + +            sync = this.bind(function () { +                var enabled = this.opts.element.attr("disabled") !== "disabled"; +                var readonly = this.opts.element.attr("readonly") === "readonly"; + +                enabled = enabled && !readonly; + +                if (this.enabled !== enabled) { +                    if (enabled) { +                        this.enable(); +                    } else { +                        this.disable(); +                    } +                } +            }); + +            // mozilla and IE +            el.bind("propertychange.select2 DOMAttrModified.select2", sync); +            // safari and chrome +            if (typeof WebKitMutationObserver !== "undefined") { +                if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; } +                this.propertyObserver = new WebKitMutationObserver(function (mutations) { +                    mutations.forEach(sync); +                }); +                this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false }); +            } +        }, + +        /** +         * Triggers the change event on the source element +         */ +        // abstract +        triggerChange: function (details) { + +            details = details || {}; +            details= $.extend({}, details, { type: "change", val: this.val() }); +            // prevents recursive triggering +            this.opts.element.data("select2-change-triggered", true); +            this.opts.element.trigger(details); +            this.opts.element.data("select2-change-triggered", false); + +            // some validation frameworks ignore the change event and listen instead to keyup, click for selects +            // so here we trigger the click event manually +            this.opts.element.click(); + +            // ValidationEngine ignorea the change event and listens instead to blur +            // so here we trigger the blur event manually if so desired +            if (this.opts.blurOnChange) +                this.opts.element.blur(); +        }, + +        // abstract +        enable: function() { +            if (this.enabled) return; + +            this.enabled=true; +            this.container.removeClass("select2-container-disabled"); +            this.opts.element.removeAttr("disabled"); +        }, + +        // abstract +        disable: function() { +            if (!this.enabled) return; + +            this.close(); + +            this.enabled=false; +            this.container.addClass("select2-container-disabled"); +            this.opts.element.attr("disabled", "disabled"); +        }, + +        // abstract +        opened: function () { +            return this.container.hasClass("select2-dropdown-open"); +        }, + +        // abstract +        positionDropdown: function() { +            var offset = this.container.offset(), +                height = this.container.outerHeight(false), +                width = this.container.outerWidth(false), +                dropHeight = this.dropdown.outerHeight(false), +	        viewPortRight = $(window).scrollLeft() + document.documentElement.clientWidth, +                viewportBottom = $(window).scrollTop() + document.documentElement.clientHeight, +                dropTop = offset.top + height, +                dropLeft = offset.left, +                enoughRoomBelow = dropTop + dropHeight <= viewportBottom, +                enoughRoomAbove = (offset.top - dropHeight) >= this.body().scrollTop(), +	        dropWidth = this.dropdown.outerWidth(false), +	        enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight, +                aboveNow = this.dropdown.hasClass("select2-drop-above"), +                bodyOffset, +                above, +                css; + +            // console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow); +            // console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body().scrollTop(), "enough?", enoughRoomAbove); + +            // fix positioning when body has an offset and is not position: static + +            if (this.body().css('position') !== 'static') { +                bodyOffset = this.body().offset(); +                dropTop -= bodyOffset.top; +                dropLeft -= bodyOffset.left; +            } + +            // always prefer the current above/below alignment, unless there is not enough room + +            if (aboveNow) { +                above = true; +                if (!enoughRoomAbove && enoughRoomBelow) above = false; +            } else { +                above = false; +                if (!enoughRoomBelow && enoughRoomAbove) above = true; +            } + +	    if (!enoughRoomOnRight) { +		   dropLeft = offset.left + width - dropWidth; +	    } + +            if (above) { +                dropTop = offset.top - dropHeight; +                this.container.addClass("select2-drop-above"); +                this.dropdown.addClass("select2-drop-above"); +            } +            else { +                this.container.removeClass("select2-drop-above"); +                this.dropdown.removeClass("select2-drop-above"); +            } + +            css = $.extend({ +                top: dropTop, +                left: dropLeft, +                width: width +            }, evaluate(this.opts.dropdownCss)); + +            this.dropdown.css(css); +        }, + +        // abstract +        shouldOpen: function() { +            var event; + +            if (this.opened()) return false; + +            event = $.Event("open"); +            this.opts.element.trigger(event); +            return !event.isDefaultPrevented(); +        }, + +        // abstract +        clearDropdownAlignmentPreference: function() { +            // clear the classes used to figure out the preference of where the dropdown should be opened +            this.container.removeClass("select2-drop-above"); +            this.dropdown.removeClass("select2-drop-above"); +        }, + +        /** +         * Opens the dropdown +         * +         * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example, +         * the dropdown is already open, or if the 'open' event listener on the element called preventDefault(). +         */ +        // abstract +        open: function () { + +            if (!this.shouldOpen()) return false; + +            window.setTimeout(this.bind(this.opening), 1); + +            return true; +        }, + +        /** +         * Performs the opening of the dropdown +         */ +        // abstract +        opening: function() { +            var cid = this.containerId, +                scroll = "scroll." + cid, +                resize = "resize."+cid, +                orient = "orientationchange."+cid, +                mask; + +            this.clearDropdownAlignmentPreference(); + +            if (this.search.val() === " ") { this.search.val(""); } + +            this.container.addClass("select2-dropdown-open").addClass("select2-container-active"); + + +            if(this.dropdown[0] !== this.body().children().last()[0]) { +                this.dropdown.detach().appendTo(this.body()); +            } + +            this.updateResults(true); + +            mask = $("#select2-drop-mask"); + +            // ensure the mask is always right before the dropdown +            if (this.dropdown.prev()[0] !== mask[0]) { +                this.dropdown.before(mask); +            } + +            // move the global id to the correct dropdown +            $("#select2-drop").removeAttr("id"); +            this.dropdown.attr("id", "select2-drop"); + +            // show the elements +            mask.css({ +                width: document.documentElement.scrollWidth, +                height: document.documentElement.scrollHeight}); +            mask.show(); +            this.dropdown.show(); +            this.positionDropdown(); + +            this.dropdown.addClass("select2-drop-active"); +            this.ensureHighlightVisible(); + +            // attach listeners to events that can change the position of the container and thus require +            // the position of the dropdown to be updated as well so it does not come unglued from the container +            this.container.parents().add(window).each(function () { +                $(this).bind(resize+" "+scroll+" "+orient, function (e) { +                    $("#select2-drop-mask").css({ +                        width:document.documentElement.scrollWidth, +                        height:document.documentElement.scrollHeight}); +                    $("#select2-drop").data("select2").positionDropdown(); +                }); +            }); + +            this.focusSearch(); +        }, + +        // abstract +        close: function () { +            if (!this.opened()) return; + +            var cid = this.containerId, +                scroll = "scroll." + cid, +                resize = "resize."+cid, +                orient = "orientationchange."+cid; + +            // unbind event listeners +            this.container.parents().add(window).each(function () { $(this).unbind(scroll).unbind(resize).unbind(orient); }); + +            this.clearDropdownAlignmentPreference(); + +            $("#select2-drop-mask").hide(); +            this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id +            this.dropdown.hide(); +            this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active"); +            this.results.empty(); +            this.clearSearch(); + +            this.opts.element.trigger($.Event("close")); +        }, + +        // abstract +        clearSearch: function () { + +        }, + +        // abstract +        ensureHighlightVisible: function () { +            var results = this.results, children, index, child, hb, rb, y, more; + +            index = this.highlight(); + +            if (index < 0) return; + +            if (index == 0) { + +                // if the first element is highlighted scroll all the way to the top, +                // that way any unselectable headers above it will also be scrolled +                // into view + +                results.scrollTop(0); +                return; +            } + +            children = this.findHighlightableChoices(); + +            child = $(children[index]); + +            hb = child.offset().top + child.outerHeight(true); + +            // if this is the last child lets also make sure select2-more-results is visible +            if (index === children.length - 1) { +                more = results.find("li.select2-more-results"); +                if (more.length > 0) { +                    hb = more.offset().top + more.outerHeight(true); +                } +            } + +            rb = results.offset().top + results.outerHeight(true); +            if (hb > rb) { +                results.scrollTop(results.scrollTop() + (hb - rb)); +            } +            y = child.offset().top - results.offset().top; + +            // make sure the top of the element is visible +            if (y < 0 && child.css('display') != 'none' ) { +                results.scrollTop(results.scrollTop() + y); // y is negative +            } +        }, + +        // abstract +        findHighlightableChoices: function() { +            var h=this.results.find(".select2-result-selectable:not(.select2-selected):not(.select2-disabled)"); +            return this.results.find(".select2-result-selectable:not(.select2-selected):not(.select2-disabled)"); +        }, + +        // abstract +        moveHighlight: function (delta) { +            var choices = this.findHighlightableChoices(), +                index = this.highlight(); + +            while (index > -1 && index < choices.length) { +                index += delta; +                var choice = $(choices[index]); +                if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) { +                    this.highlight(index); +                    break; +                } +            } +        }, + +        // abstract +        highlight: function (index) { +            var choices = this.findHighlightableChoices(); + +            if (arguments.length === 0) { +                return indexOf(choices.filter(".select2-highlighted")[0], choices.get()); +            } + +            if (index >= choices.length) index = choices.length - 1; +            if (index < 0) index = 0; + +            this.results.find(".select2-highlighted").removeClass("select2-highlighted"); + +            $(choices[index]).addClass("select2-highlighted"); +            this.ensureHighlightVisible(); +        }, + +        // abstract +        countSelectableResults: function() { +            return this.findHighlightableChoices().length; +        }, + +        // abstract +        highlightUnderEvent: function (event) { +            var el = $(event.target).closest(".select2-result-selectable"); +            if (el.length > 0 && !el.is(".select2-highlighted")) { +        		var choices = this.findHighlightableChoices(); +                this.highlight(choices.index(el)); +            } else if (el.length == 0) { +                // if we are over an unselectable item remove al highlights +                this.results.find(".select2-highlighted").removeClass("select2-highlighted"); +            } +        }, + +        // abstract +        loadMoreIfNeeded: function () { +            var results = this.results, +                more = results.find("li.select2-more-results"), +                below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible +                offset = -1, // index of first element without data +                page = this.resultsPage + 1, +                self=this, +                term=this.search.val(), +                context=this.context; + +            if (more.length === 0) return; +            below = more.offset().top - results.offset().top - results.height(); + +            if (below <= this.opts.loadMorePadding) { +                more.addClass("select2-active"); +                this.opts.query({ +                        term: term, +                        page: page, +                        context: context, +                        matcher: this.opts.matcher, +                        callback: this.bind(function (data) { + +                    // ignore a response if the select2 has been closed before it was received +                    if (!self.opened()) return; + + +                    self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context}); + +                    if (data.more===true) { +                        more.detach().appendTo(results).text(self.opts.formatLoadMore(page+1)); +                        window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10); +                    } else { +                        more.remove(); +                    } +                    self.positionDropdown(); +                    self.resultsPage = page; +                })}); +            } +        }, + +        /** +         * Default tokenizer function which does nothing +         */ +        tokenize: function() { + +        }, + +        /** +         * @param initial whether or not this is the call to this method right after the dropdown has been opened +         */ +        // abstract +        updateResults: function (initial) { +            var search = this.search, results = this.results, opts = this.opts, data, self=this, input; + +            // if the search is currently hidden we do not alter the results +            if (initial !== true && (this.showSearchInput === false || !this.opened())) { +                return; +            } + +            search.addClass("select2-active"); + +            function postRender() { +                results.scrollTop(0); +                search.removeClass("select2-active"); +                self.positionDropdown(); +            } + +            function render(html) { +                results.html(html); +                postRender(); +            } + +            if (opts.maximumSelectionSize >=1) { +                data = this.data(); +                if ($.isArray(data) && data.length >= opts.maximumSelectionSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) { +            	    render("<li class='select2-selection-limit'>" + opts.formatSelectionTooBig(opts.maximumSelectionSize) + "</li>"); +            	    return; +                } +            } + +            if (search.val().length < opts.minimumInputLength) { +                if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) { +                    render("<li class='select2-no-results'>" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>"); +                } else { +                    render(""); +                } +                return; +            } +            else if (opts.formatSearching() && initial===true) { +                render("<li class='select2-searching'>" + opts.formatSearching() + "</li>"); +            } + +            if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) { +                if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) { +                    render("<li class='select2-no-results'>" + opts.formatInputTooLong(search.val(), opts.maximumInputLength) + "</li>"); +                } else { +                    render(""); +                } +                return; +            } + +            // give the tokenizer a chance to pre-process the input +            input = this.tokenize(); +            if (input != undefined && input != null) { +                search.val(input); +            } + +            this.resultsPage = 1; +            opts.query({ +                    term: search.val(), +                    page: this.resultsPage, +                    context: null, +                    matcher: opts.matcher, +                    callback: this.bind(function (data) { +                var def; // default choice + +                // ignore a response if the select2 has been closed before it was received +                if (!this.opened()) return; + +                // save context, if any +                this.context = (data.context===undefined) ? null : data.context; + +                // create a default choice and prepend it to the list +                if (this.opts.createSearchChoice && search.val() !== "") { +                    def = this.opts.createSearchChoice.call(null, search.val(), data.results); +                    if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) { +                        if ($(data.results).filter( +                            function () { +                                return equal(self.id(this), self.id(def)); +                            }).length === 0) { +                            data.results.unshift(def); +                        } +                    } +                } + +                if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) { +                    render("<li class='select2-no-results'>" + opts.formatNoMatches(search.val()) + "</li>"); +                    return; +                } + +                results.empty(); +                self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null}); + +                if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) { +                    results.append("<li class='select2-more-results'>" + self.opts.escapeMarkup(opts.formatLoadMore(this.resultsPage)) + "</li>"); +                    window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10); +                } + +                this.postprocessResults(data, initial); + +                postRender(); +            })}); +        }, + +        // abstract +        cancel: function () { +            this.close(); +        }, + +        // abstract +        blur: function () { +            // if selectOnBlur == true, select the currently highlighted option +            if (this.opts.selectOnBlur) +                this.selectHighlighted({noFocus: true}); + +            this.close(); +            this.container.removeClass("select2-container-active"); +            this.dropdown.removeClass("select2-drop-active"); +            // synonymous to .is(':focus'), which is available in jquery >= 1.6 +            if (this.search[0] === document.activeElement) { this.search.blur(); } +            this.clearSearch(); +            this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); +            this.opts.element.triggerHandler("blur"); +        }, + +        // abstract +        focusSearch: function () { +            // need to do it here as well as in timeout so it works in IE +            this.search.show(); +            this.search.focus(); + +            /* we do this in a timeout so that current event processing can complete before this code is executed. +             this makes sure the search field is focussed even if the current event would blur it */ +            window.setTimeout(this.bind(function () { +                // reset the value so IE places the cursor at the end of the input box +                this.search.show(); +                this.search.focus(); +                this.search.val(this.search.val()); +            }), 10); +        }, + +        // abstract +        selectHighlighted: function (options) { +            var index=this.highlight(), +                highlighted=this.results.find(".select2-highlighted"), +                data = highlighted.closest('.select2-result').data("select2-data"); + +            if (data) { +                this.highlight(index); +                this.onSelect(data, options); +            } +        }, + +        // abstract +        getPlaceholder: function () { + +            // if a placeholder is specified on a select without the first empty option ignore it +            if (this.select) { +               if (this.select.find("option").first().text() !== "") { +                   return undefined; +               } +            } + +            return this.opts.element.attr("placeholder") || +                this.opts.element.attr("data-placeholder") || // jquery 1.4 compat +                this.opts.element.data("placeholder") || +                this.opts.placeholder; +        }, + +        /** +         * Get the desired width for the container element.  This is +         * derived first from option `width` passed to select2, then +         * the inline 'style' on the original element, and finally +         * falls back to the jQuery calculated element width. +         */ +        // abstract +        initContainerWidth: function () { +            function resolveContainerWidth() { +                var style, attrs, matches, i, l; + +                if (this.opts.width === "off") { +                    return null; +                } else if (this.opts.width === "element"){ +                    return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px'; +                } else if (this.opts.width === "copy" || this.opts.width === "resolve") { +                    // check if there is inline style on the element that contains width +                    style = this.opts.element.attr('style'); +                    if (style !== undefined) { +                        attrs = style.split(';'); +                        for (i = 0, l = attrs.length; i < l; i = i + 1) { +                            matches = attrs[i].replace(/\s/g, '') +                                .match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/); +                            if (matches !== null && matches.length >= 1) +                                return matches[1]; +                        } +                    } + +                    if (this.opts.width === "resolve") { +                        // next check if css('width') can resolve a width that is percent based, this is sometimes possible +                        // when attached to input type=hidden or elements hidden via css +                        style = this.opts.element.css('width'); +                        if (style.indexOf("%") > 0) return style; + +                        // finally, fallback on the calculated width of the element +                        return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px'); +                    } + +                    return null; +                } else if ($.isFunction(this.opts.width)) { +                    return this.opts.width(); +                } else { +                    return this.opts.width; +               } +            }; + +            var width = resolveContainerWidth.call(this); +            if (width !== null) { +                this.container.attr("style", "width: "+width); +            } +        } +    }); + +    SingleSelect2 = clazz(AbstractSelect2, { + +        // single + +		createContainer: function () { +            var container = $(document.createElement("div")).attr({ +                "class": "select2-container" +            }).html([ +                "    <a href='javascript:void(0)' onclick='return false;' class='select2-choice'>", +                "   <span></span><abbr class='select2-search-choice-close' style='display:none;'></abbr>", +                "   <div><b></b></div>" , +                "</a>", +                "    <div class='select2-drop select2-offscreen'>" , +                "   <div class='select2-search'>" , +                "       <input type='text' autocomplete='off' class='select2-input'/>" , +                "   </div>" , +                "   <ul class='select2-results'>" , +                "   </ul>" , +                "</div>"].join("")); +            return container; +        }, + +        // single +        opening: function () { +            this.search.show(); +            this.parent.opening.apply(this, arguments); +            this.dropdown.removeClass("select2-offscreen"); +        }, + +        // single +        close: function () { +            if (!this.opened()) return; +            this.parent.close.apply(this, arguments); +            this.dropdown.removeAttr("style").addClass("select2-offscreen").insertAfter(this.selection).show(); +        }, + +        // single +        focus: function () { +            this.close(); +            this.selection.focus(); +        }, + +        // single +        isFocused: function () { +            return this.selection[0] === document.activeElement; +        }, + +        // single +        cancel: function () { +            this.parent.cancel.apply(this, arguments); +            this.selection.focus(); +        }, + +        // single +        initContainer: function () { + +            var selection, +                container = this.container, +                dropdown = this.dropdown, +                clickingInside = false; + +            this.selection = selection = container.find(".select2-choice"); + +            this.search.bind("keydown", this.bind(function (e) { +                if (!this.enabled) return; + +                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { +                    // prevent the page from scrolling +                    killEvent(e); +                    return; +                } + +                if (this.opened()) { +                    switch (e.which) { +                        case KEY.UP: +                        case KEY.DOWN: +                            this.moveHighlight((e.which === KEY.UP) ? -1 : 1); +                            killEvent(e); +                            return; +                        case KEY.TAB: +                        case KEY.ENTER: +                            this.selectHighlighted(); +                            killEvent(e); +                            return; +                        case KEY.ESC: +                            this.cancel(e); +                            killEvent(e); +                            return; +                    } +                } else { + +                    if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) { +                        return; +                    } + +                    if (this.opts.openOnEnter === false && e.which === KEY.ENTER) { +                        return; +                    } + +                    this.open(); + +                    if (e.which === KEY.ENTER) { +                        // do not propagate the event otherwise we open, and propagate enter which closes +                        return; +                    } +                } +            })); + +            this.search.bind("focus", this.bind(function() { +                this.selection.attr("tabIndex", "-1"); +            })); +            this.search.bind("blur", this.bind(function() { +                if (!this.opened()) this.container.removeClass("select2-container-active"); +                window.setTimeout(this.bind(function() { +                    // restore original tab index +                    var ti=this.elementTabIndex || 0; +                    if (ti) { +                        this.selection.attr("tabIndex", ti); +                    } else { +                        this.selection.removeAttr("tabIndex"); +                    } +                }), 10); +            })); + +            selection.delegate("abbr", "mousedown", this.bind(function (e) { +                if (!this.enabled) return; +                this.clear(); +                killEventImmediately(e); +                this.close(); +                this.triggerChange(); +                this.selection.focus(); +            })); + +            selection.bind("mousedown", this.bind(function (e) { +                clickingInside = true; + +                if (this.opened()) { +                    this.close(); +                    this.selection.focus(); +                } else if (this.enabled) { +                    this.open(); +                } + +                clickingInside = false; +            })); + +            dropdown.bind("mousedown", this.bind(function() { this.search.focus(); })); + +            selection.bind("focus", this.bind(function() { +                this.container.addClass("select2-container-active"); +                // hide the search so the tab key does not focus on it +                this.search.attr("tabIndex", "-1"); +            })); + +            selection.bind("blur", this.bind(function() { +                if (!this.opened()) { +                    this.container.removeClass("select2-container-active"); +                } +                window.setTimeout(this.bind(function() { this.search.attr("tabIndex", this.elementTabIndex || 0); }), 10); +            })); + +            selection.bind("keydown", this.bind(function(e) { +                if (!this.enabled) return; + +                if (e.which == KEY.DOWN || e.which == KEY.UP +                    || (e.which == KEY.ENTER && this.opts.openOnEnter)) { +                    this.open(); +                    killEvent(e); +                    return; +                } + +                if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) { +                    if (this.opts.allowClear) { +                        this.clear(); +                    } +                    killEvent(e); +                    return; +                } +            })); +            selection.bind("keypress", this.bind(function(e) { +		if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE || e.which == KEY.TAB || e.which == KEY.ENTER || e.which == 0) { +			return +		} +                var key = String.fromCharCode(e.which); +                this.search.val(key); +                this.open(); +            })); + +            this.setPlaceholder(); + +            this.search.bind("focus", this.bind(function() { +                this.container.addClass("select2-container-active"); +            })); +        }, + +        // single +        clear: function() { +            this.opts.element.val(""); +            this.selection.find("span").empty(); +            this.selection.removeData("select2-data"); +            this.setPlaceholder(); +        }, + +        /** +         * Sets selection based on source element's value +         */ +        // single +        initSelection: function () { +            var selected; +            if (this.opts.element.val() === "" && this.opts.element.text() === "") { +                this.close(); +                this.setPlaceholder(); +            } else { +                var self = this; +                this.opts.initSelection.call(null, this.opts.element, function(selected){ +                    if (selected !== undefined && selected !== null) { +                        self.updateSelection(selected); +                        self.close(); +                        self.setPlaceholder(); +                    } +                }); +            } +        }, + +        // single +        prepareOpts: function () { +            var opts = this.parent.prepareOpts.apply(this, arguments); + +            if (opts.element.get(0).tagName.toLowerCase() === "select") { +                // install the selection initializer +                opts.initSelection = function (element, callback) { +                    var selected = element.find(":selected"); +                    // a single select box always has a value, no need to null check 'selected' +                    if ($.isFunction(callback)) +                        callback({id: selected.attr("value"), text: selected.text(), element:selected}); +                }; +            } else if ("data" in opts) { +                // install default initSelection when applied to hidden input and data is local +                opts.initSelection = opts.initSelection || function (element, callback) { +                    var id = element.val(); +                    //search in data by id +                    opts.query({ +                        matcher: function(term, text, el){ +                            return equal(id, opts.id(el)); +                        }, +                        callback: !$.isFunction(callback) ? $.noop : function(filtered) { +                            callback(filtered.results.length ? filtered.results[0] : null); +                        } +                    }); +                }; +            } + +            return opts; +        }, + +        // single +        setPlaceholder: function () { +            var placeholder = this.getPlaceholder(); + +            if (this.opts.element.val() === "" && placeholder !== undefined) { + +                // check for a first blank option if attached to a select +                if (this.select && this.select.find("option:first").text() !== "") return; + +                this.selection.find("span").html(this.opts.escapeMarkup(placeholder)); + +                this.selection.addClass("select2-default"); + +                this.selection.find("abbr").hide(); +            } +        }, + +        // single +        postprocessResults: function (data, initial) { +            var selected = 0, self = this, showSearchInput = true; + +            // find the selected element in the result list + +            this.findHighlightableChoices().each2(function (i, elm) { +                if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) { +                    selected = i; +                    return false; +                } +            }); + +            // and highlight it + +            this.highlight(selected); + +            // hide the search box if this is the first we got the results and there are a few of them + +            if (initial === true) { +                showSearchInput = this.showSearchInput = countResults(data.results) >= this.opts.minimumResultsForSearch; +                this.dropdown.find(".select2-search")[showSearchInput ? "removeClass" : "addClass"]("select2-search-hidden"); + +                //add "select2-with-searchbox" to the container if search box is shown +                $(this.dropdown, this.container)[showSearchInput ? "addClass" : "removeClass"]("select2-with-searchbox"); +            } + +        }, + +        // single +        onSelect: function (data, options) { +            var old = this.opts.element.val(); + +            this.opts.element.val(this.id(data)); +            this.updateSelection(data); +            this.close(); + +            if (!options || !options.noFocus) +                this.selection.focus(); + +            if (!equal(old, this.id(data))) { this.triggerChange(); } +        }, + +        // single +        updateSelection: function (data) { + +            var container=this.selection.find("span"), formatted; + +            this.selection.data("select2-data", data); + +            container.empty(); +            formatted=this.opts.formatSelection(data, container); +            if (formatted !== undefined) { +                container.append(this.opts.escapeMarkup(formatted)); +            } + +            this.selection.removeClass("select2-default"); + +            if (this.opts.allowClear && this.getPlaceholder() !== undefined) { +                this.selection.find("abbr").show(); +            } +        }, + +        // single +        val: function () { +            var val, triggerChange = false, data = null, self = this; + +            if (arguments.length === 0) { +                return this.opts.element.val(); +            } + +            val = arguments[0]; + +            if (arguments.length > 1) { +                triggerChange = arguments[1]; +            } + +            if (this.select) { +                this.select +                    .val(val) +                    .find(":selected").each2(function (i, elm) { +                        data = {id: elm.attr("value"), text: elm.text()}; +                        return false; +                    }); +                this.updateSelection(data); +                this.setPlaceholder(); +                if (triggerChange) { +                    this.triggerChange(); +                } +            } else { +                if (this.opts.initSelection === undefined) { +                    throw new Error("cannot call val() if initSelection() is not defined"); +                } +                // val is an id. !val is true for [undefined,null,'',0] - 0 is legal +                if (!val && val !== 0) { +                    this.clear(); +                    if (triggerChange) { +                        this.triggerChange(); +                    } +                    return; +                } +                this.opts.element.val(val); +                this.opts.initSelection(this.opts.element, function(data){ +                    self.opts.element.val(!data ? "" : self.id(data)); +                    self.updateSelection(data); +                    self.setPlaceholder(); +                    self.triggerChange(); +                }); +            } +        }, + +        // single +        clearSearch: function () { +            this.search.val(""); +        }, + +        // single +        data: function(value) { +            var data; + +            if (arguments.length === 0) { +                data = this.selection.data("select2-data"); +                if (data == undefined) data = null; +                return data; +            } else { +                if (!value || value === "") { +                    this.clear(); +                } else { +                    this.opts.element.val(!value ? "" : this.id(value)); +                    this.updateSelection(value); +                } +            } +        } +    }); + +    MultiSelect2 = clazz(AbstractSelect2, { + +        // multi +        createContainer: function () { +            var container = $(document.createElement("div")).attr({ +                "class": "select2-container select2-container-multi" +            }).html([ +                "    <ul class='select2-choices'>", +                //"<li class='select2-search-choice'><span>California</span><a href="javascript:void(0)" class="select2-search-choice-close"></a></li>" , +                "  <li class='select2-search-field'>" , +                "    <input type='text' autocomplete='off' class='select2-input'>" , +                "  </li>" , +                "</ul>" , +                "<div class='select2-drop select2-drop-multi' style='display:none;'>" , +                "   <ul class='select2-results'>" , +                "   </ul>" , +                "</div>"].join("")); +			return container; +        }, + +        // multi +        prepareOpts: function () { +            var opts = this.parent.prepareOpts.apply(this, arguments); + +            // TODO validate placeholder is a string if specified + +            if (opts.element.get(0).tagName.toLowerCase() === "select") { +                // install sthe selection initializer +                opts.initSelection = function (element,callback) { + +                    var data = []; +                    element.find(":selected").each2(function (i, elm) { +                        data.push({id: elm.attr("value"), text: elm.text(), element: elm}); +                    }); + +                    if ($.isFunction(callback)) +                        callback(data); +                }; +            } else if ("data" in opts) { +                // install default initSelection when applied to hidden input and data is local +                opts.initSelection = opts.initSelection || function (element, callback) { +                    var ids = splitVal(element.val(), opts.separator); +                    //search in data by array of ids +                    opts.query({ +                        matcher: function(term, text, el){ +                            return $.grep(ids, function(id) { +                                return equal(id, opts.id(el)); +                            }).length; +                        }, +                        callback: !$.isFunction(callback) ? $.noop : function(filtered) { +                            callback(filtered.results); +                        } +                    }); +                }; +            } + +            return opts; +        }, + +        // multi +        initContainer: function () { + +            var selector = ".select2-choices", selection; + +            this.searchContainer = this.container.find(".select2-search-field"); +            this.selection = selection = this.container.find(selector); + +            this.search.bind("keydown", this.bind(function (e) { +                if (!this.enabled) return; + +                if (e.which === KEY.BACKSPACE && this.search.val() === "") { +                    this.close(); + +                    var choices, +                        selected = selection.find(".select2-search-choice-focus"); +                    if (selected.length > 0) { +                        this.unselect(selected.first()); +                        this.search.width(10); +                        killEvent(e); +                        return; +                    } + +                    choices = selection.find(".select2-search-choice:not(.select2-locked)"); +                    if (choices.length > 0) { +                        choices.last().addClass("select2-search-choice-focus"); +                    } +                } else { +                    selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); +                } + +                if (this.opened()) { +                    switch (e.which) { +                    case KEY.UP: +                    case KEY.DOWN: +                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1); +                        killEvent(e); +                        return; +                    case KEY.ENTER: +                    case KEY.TAB: +                        this.selectHighlighted(); +                        killEvent(e); +                        return; +                    case KEY.ESC: +                        this.cancel(e); +                        killEvent(e); +                        return; +                    } +                } + +                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) +                 || e.which === KEY.BACKSPACE || e.which === KEY.ESC) { +                    return; +                } + +                if (this.opts.openOnEnter === false && e.which === KEY.ENTER) { +                    return; +                } + +                this.open(); + +                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { +                    // prevent the page from scrolling +                    killEvent(e); +                } +            })); + +            this.search.bind("keyup", this.bind(this.resizeSearch)); + +            this.search.bind("blur", this.bind(function(e) { +                this.container.removeClass("select2-container-active"); +                this.search.removeClass("select2-focused"); +                this.clearSearch(); +                e.stopImmediatePropagation(); +            })); + +            this.container.delegate(selector, "mousedown", this.bind(function (e) { +                if (!this.enabled) return; +                if ($(e.target).closest(".select2-search-choice").length > 0) { +                    // clicked inside a select2 search choice, do not open +                    return; +                } +                this.clearPlaceholder(); +                this.open(); +                this.focusSearch(); +                e.preventDefault(); +            })); + +            this.container.delegate(selector, "focus", this.bind(function () { +                if (!this.enabled) return; +                this.container.addClass("select2-container-active"); +                this.dropdown.addClass("select2-drop-active"); +                this.clearPlaceholder(); +            })); + +            // set the placeholder if necessary +            this.clearSearch(); +        }, + +        // multi +        enable: function() { +            if (this.enabled) return; + +            this.parent.enable.apply(this, arguments); + +            this.search.removeAttr("disabled"); +        }, + +        // multi +        disable: function() { +            if (!this.enabled) return; + +            this.parent.disable.apply(this, arguments); + +            this.search.attr("disabled", true); +        }, + +        // multi +        initSelection: function () { +            var data; +            if (this.opts.element.val() === "" && this.opts.element.text() === "") { +                this.updateSelection([]); +                this.close(); +                // set the placeholder if necessary +                this.clearSearch(); +            } +            if (this.select || this.opts.element.val() !== "") { +                var self = this; +                this.opts.initSelection.call(null, this.opts.element, function(data){ +                    if (data !== undefined && data !== null) { +                        self.updateSelection(data); +                        self.close(); +                        // set the placeholder if necessary +                        self.clearSearch(); +                    } +                }); +            } +        }, + +        // multi +        clearSearch: function () { +            var placeholder = this.getPlaceholder(); + +            if (placeholder !== undefined  && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) { +                this.search.val(placeholder).addClass("select2-default"); +                // stretch the search box to full width of the container so as much of the placeholder is visible as possible +                this.resizeSearch(); +            } else { +                // we set this to " " instead of "" and later clear it on focus() because there is a firefox bug +                // that does not properly render the caret when the field starts out blank +                this.search.val(" ").width(10); +            } +        }, + +        // multi +        clearPlaceholder: function () { +            if (this.search.hasClass("select2-default")) { +                this.search.val("").removeClass("select2-default"); +            } else { +                // work around for the space character we set to avoid firefox caret bug +                if (this.search.val() === " ") this.search.val(""); +            } +        }, + +        // multi +        opening: function () { +            this.parent.opening.apply(this, arguments); + +            this.clearPlaceholder(); +			this.resizeSearch(); +            this.focusSearch(); +        }, + +        // multi +        close: function () { +            if (!this.opened()) return; +            this.parent.close.apply(this, arguments); +        }, + +        // multi +        focus: function () { +            this.close(); +            this.search.focus(); +        }, + +        // multi +        isFocused: function () { +            return this.search.hasClass("select2-focused"); +        }, + +        // multi +        updateSelection: function (data) { +            var ids = [], filtered = [], self = this; + +            // filter out duplicates +            $(data).each(function () { +                if (indexOf(self.id(this), ids) < 0) { +                    ids.push(self.id(this)); +                    filtered.push(this); +                } +            }); +            data = filtered; + +            this.selection.find(".select2-search-choice").remove(); +            $(data).each(function () { +                self.addSelectedChoice(this); +            }); +            self.postprocessResults(); +        }, + +        tokenize: function() { +            var input = this.search.val(); +            input = this.opts.tokenizer(input, this.data(), this.bind(this.onSelect), this.opts); +            if (input != null && input != undefined) { +                this.search.val(input); +                if (input.length > 0) { +                    this.open(); +                } +            } + +        }, + +        // multi +        onSelect: function (data, options) { +            this.addSelectedChoice(data); +            if (this.select || !this.opts.closeOnSelect) this.postprocessResults(); + +            if (this.opts.closeOnSelect) { +                this.close(); +                this.search.width(10); +            } else { +                if (this.countSelectableResults()>0) { +                    this.search.width(10); +                    this.resizeSearch(); +                    this.positionDropdown(); +                } else { +                    // if nothing left to select close +                    this.close(); +                } +            } + +            // since its not possible to select an element that has already been +            // added we do not need to check if this is a new element before firing change +            this.triggerChange({ added: data }); + +            if (!options || !options.noFocus) +                this.focusSearch(); +        }, + +        // multi +        cancel: function () { +            this.close(); +            this.focusSearch(); +        }, + +        addSelectedChoice: function (data) { +            var enableChoice = !data.locked, +                enabledItem = $( +                    "<li class='select2-search-choice'>" + +                    "    <div></div>" + +                    "    <a href='#' onclick='return false;' class='select2-search-choice-close' tabindex='-1'></a>" + +                    "</li>"), +                disabledItem = $( +                    "<li class='select2-search-choice select2-locked'>" + +                    "<div></div>" + +                    "</li>"); +            var choice = enableChoice ? enabledItem : disabledItem, +                id = this.id(data), +                val = this.getVal(), +                formatted; + +            formatted=this.opts.formatSelection(data, choice.find("div")); +            if (formatted != undefined) { +                choice.find("div").replaceWith("<div>"+this.opts.escapeMarkup(formatted)+"</div>"); +            } + +            if(enableChoice){ +              choice.find(".select2-search-choice-close") +                  .bind("mousedown", killEvent) +                  .bind("click dblclick", this.bind(function (e) { +                  if (!this.enabled) return; + +                  $(e.target).closest(".select2-search-choice").fadeOut('fast', this.bind(function(){ +                      this.unselect($(e.target)); +                      this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); +                      this.close(); +                      this.focusSearch(); +                  })).dequeue(); +                  killEvent(e); +              })).bind("focus", this.bind(function () { +                  if (!this.enabled) return; +                  this.container.addClass("select2-container-active"); +                  this.dropdown.addClass("select2-drop-active"); +              })); +            } + +            choice.data("select2-data", data); +            choice.insertBefore(this.searchContainer); + +            val.push(id); +            this.setVal(val); +        }, + +        // multi +        unselect: function (selected) { +            var val = this.getVal(), +                data, +                index; + +            selected = selected.closest(".select2-search-choice"); + +            if (selected.length === 0) { +                throw "Invalid argument: " + selected + ". Must be .select2-search-choice"; +            } + +            data = selected.data("select2-data"); + +            if (!data) { +                // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued +                // and invoked on an element already removed +                return; +            } + +            index = indexOf(this.id(data), val); + +            if (index >= 0) { +                val.splice(index, 1); +                this.setVal(val); +                if (this.select) this.postprocessResults(); +            } +            selected.remove(); +            this.triggerChange({ removed: data }); +        }, + +        // multi +        postprocessResults: function () { +            var val = this.getVal(), +                choices = this.results.find(".select2-result"), +                compound = this.results.find(".select2-result-with-children"), +                self = this; + +            choices.each2(function (i, choice) { +                var id = self.id(choice.data("select2-data")); +                if (indexOf(id, val) >= 0) { +                    choice.addClass("select2-selected"); +                    // mark all children of the selected parent as selected +                    choice.find(".select2-result-selectable").addClass("select2-selected"); +                } +            }); + +            compound.each2(function(i, choice) { +                // hide an optgroup if it doesnt have any selectable children +                if (!choice.is('.select2-result-selectable') +                    && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) { +                    choice.addClass("select2-selected"); +                } +            }); + +            if (this.highlight() == -1){ +                self.highlight(0); +            } + +        }, + +        // multi +        resizeSearch: function () { + +            var minimumWidth, left, maxWidth, containerLeft, searchWidth, +            	sideBorderPadding = getSideBorderPadding(this.search); + +            minimumWidth = measureTextWidth(this.search) + 10; + +            left = this.search.offset().left; + +            maxWidth = this.selection.width(); +            containerLeft = this.selection.offset().left; + +            searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding; +            if (searchWidth < minimumWidth) { +                searchWidth = maxWidth - sideBorderPadding; +            } + +            if (searchWidth < 40) { +                searchWidth = maxWidth - sideBorderPadding; +            } + +            if (searchWidth <= 0) { +              searchWidth = minimumWidth +            } + +            this.search.width(searchWidth); +        }, + +        // multi +        getVal: function () { +            var val; +            if (this.select) { +                val = this.select.val(); +                return val === null ? [] : val; +            } else { +                val = this.opts.element.val(); +                return splitVal(val, this.opts.separator); +            } +        }, + +        // multi +        setVal: function (val) { +            var unique; +            if (this.select) { +                this.select.val(val); +            } else { +                unique = []; +                // filter out duplicates +                $(val).each(function () { +                    if (indexOf(this, unique) < 0) unique.push(this); +                }); +                this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator)); +            } +        }, + +        // multi +        val: function () { +            var val, triggerChange = false, data = [], self=this; + +            if (arguments.length === 0) { +                return this.getVal(); +            } + +            val = arguments[0]; + +            if (arguments.length > 1) { +                triggerChange = arguments[1]; +            } + +            // val is an id. !val is true for [undefined,null,'',0] - 0 is legal +            if (!val && val !== 0) { +                this.opts.element.val(""); +                this.updateSelection([]); +                this.clearSearch(); +                if (triggerChange) { +                    this.triggerChange(); +                } +                return; +            } + +            // val is a list of ids +            this.setVal(val); + +            if (this.select) { +                this.select.find(":selected").each(function () { +                    data.push({id: $(this).attr("value"), text: $(this).text()}); +                }); +                this.updateSelection(data); +                if (triggerChange) { +                    this.triggerChange(); +                } +            } else { +                if (this.opts.initSelection === undefined) { +                    throw new Error("val() cannot be called if initSelection() is not defined") +                } + +                this.opts.initSelection(this.opts.element, function(data){ +                    var ids=$(data).map(self.id); +                    self.setVal(ids); +                    self.updateSelection(data); +                    self.clearSearch(); +                    if (triggerChange) { +                        self.triggerChange(); +                    } +                }); +            } +            this.clearSearch(); +        }, + +        // multi +        onSortStart: function() { +            if (this.select) { +                throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead."); +            } + +            // collapse search field into 0 width so its container can be collapsed as well +            this.search.width(0); +            // hide the container +            this.searchContainer.hide(); +        }, + +        // multi +        onSortEnd:function() { + +            var val=[], self=this; + +            // show search and move it to the end of the list +            this.searchContainer.show(); +            // make sure the search container is the last item in the list +            this.searchContainer.appendTo(this.searchContainer.parent()); +            // since we collapsed the width in dragStarted, we resize it here +            this.resizeSearch(); + +            // update selection + +            this.selection.find(".select2-search-choice").each(function() { +                val.push(self.opts.id($(this).data("select2-data"))); +            }); +            this.setVal(val); +            this.triggerChange(); +        }, + +        // multi +        data: function(values) { +            var self=this, ids; +            if (arguments.length === 0) { +                 return this.selection +                     .find(".select2-search-choice") +                     .map(function() { return $(this).data("select2-data"); }) +                     .get(); +            } else { +                if (!values) { values = []; } +                ids = $.map(values, function(e) { return self.opts.id(e)}); +                this.setVal(ids); +                this.updateSelection(values); +                this.clearSearch(); +            } +        } +    }); + +    $.fn.select2 = function () { + +        var args = Array.prototype.slice.call(arguments, 0), +            opts, +            select2, +            value, multiple, allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "onSortStart", "onSortEnd", "enable", "disable", "positionDropdown", "data"]; + +        this.each(function () { +            if (args.length === 0 || typeof(args[0]) === "object") { +                opts = args.length === 0 ? {} : $.extend({}, args[0]); +                opts.element = $(this); + +                if (opts.element.get(0).tagName.toLowerCase() === "select") { +                    multiple = opts.element.attr("multiple"); +                } else { +                    multiple = opts.multiple || false; +                    if ("tags" in opts) {opts.multiple = multiple = true;} +                } + +                select2 = multiple ? new MultiSelect2() : new SingleSelect2(); +                select2.init(opts); +            } else if (typeof(args[0]) === "string") { + +                if (indexOf(args[0], allowedMethods) < 0) { +                    throw "Unknown method: " + args[0]; +                } + +                value = undefined; +                select2 = $(this).data("select2"); +                if (select2 === undefined) return; +                if (args[0] === "container") { +                    value=select2.container; +                } else { +                    value = select2[args[0]].apply(select2, args.slice(1)); +                } +                if (value !== undefined) {return false;} +            } else { +                throw "Invalid arguments to select2 plugin: " + args; +            } +        }); +        return (value === undefined) ? this : value; +    }; + +    // plugin defaults, accessible to users +    $.fn.select2.defaults = { +        width: "copy", +        loadMorePadding: 0, +        closeOnSelect: true, +        openOnEnter: true, +        containerCss: {}, +        dropdownCss: {}, +        containerCssClass: "", +        dropdownCssClass: "", +        formatResult: function(result, container, query) { +            var markup=[]; +            markMatch(result.text, query.term, markup, this.escapeMarkup); +            return markup.join(""); +        }, +        formatSelection: function (data, container) { +            return data ? data.text : undefined; +        }, +        sortResults: function (results, container, query) { +            return results; +        }, +        formatResultCssClass: function(data) {return undefined;}, +        formatNoMatches: function () { return "No matches found"; }, +        formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1? "" : "s"); }, +        formatInputTooLong: function (input, max) { var n = input.length - max; return "Please enter " + n + " less character" + (n == 1? "" : "s"); }, +        formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); }, +        formatLoadMore: function (pageNumber) { return "Loading more results..."; }, +        formatSearching: function () { return "Searching..."; }, +        minimumResultsForSearch: 0, +        minimumInputLength: 0, +        maximumInputLength: null, +        maximumSelectionSize: 0, +        id: function (e) { return e.id; }, +        matcher: function(term, text) { +            return text.toUpperCase().indexOf(term.toUpperCase()) >= 0; +        }, +        separator: ",", +        tokenSeparators: [], +        tokenizer: defaultTokenizer, +        escapeMarkup: function (markup) { +            var replace_map = { +                '\\': '\', +                '&': '&', +                '<': '<', +                '>': '>', +                '"': '"', +                "'": ''', +                "/": '/' +            }; + +            return String(markup).replace(/[&<>"'/\\]/g, function (match) { +                    return replace_map[match[0]]; +            }); +        }, +        blurOnChange: false, +        selectOnBlur: false +    }; + +    // exports +    window.Select2 = { +        query: { +            ajax: ajax, +            local: local, +            tags: tags +        }, util: { +            debounce: debounce, +            markMatch: markMatch +        }, "class": { +            "abstract": AbstractSelect2, +            "single": SingleSelect2, +            "multi": MultiSelect2 +        } +    }; + +}(jQuery)); diff --git a/module/web/templates/default/base.html b/module/web/templates/default/base.html index 320732b3f..48b18e368 100644 --- a/module/web/templates/default/base.html +++ b/module/web/templates/default/base.html @@ -10,7 +10,9 @@      <!-- TODO Include this font -->
      <link href="http://fonts.googleapis.com/css?family=Abel" rel="stylesheet" type="text/css"/>
      <link href="/static/css/bootstrap.css" rel="stylesheet" type="text/css"/>
 +    <link href="/static/css/select2.css" rel="stylesheet" type="text/css"/>
      <link href="/static/css/font.css" rel="stylesheet" type="text/css"/>
 +    <link href="static/css/fontawesome.css" rel="stylesheet" type="text/css"/>
      <link href="/static/css/default/style.less" rel="stylesheet/less" type="text/css" media="screen"/>
      {% block css %}
      {% endblock %}
 @@ -54,7 +56,7 @@                                  class="caret"></span></a>
                          <ul class="dropdown-menu" style="right: 0; left: -100%">
                              <li><a href="#"><i class="icon-pencil"></i> Edit</a></li>
 -                            <li><a href="#"><i class="icon-trash"></i> Delete</a></li>                          
 +                            <li><a href="#"><i class="icon-trash"></i> Delete</a></li>
                              <li class="divider"></li>
                              <li><a href="/admin"><i class="icon-star"></i> Admin</a></li>
                              <li><a href="/settings"><i class="icon-wrench"></i> Settings</a></li>
 @@ -81,6 +83,7 @@                  <div id="progress-area" style="margin-top: 16px">
                      No running tasks
                      <i class="icon-white icon-tasks pull-right"></i>
 +
                      <div class="progress" id="globalprogress">
                          <div class="bar" style="width: 48%">48%</div>
                      </div>
 @@ -101,6 +104,7 @@                                      <li>
                                          Some Download
                                          <span class="pull-right">YouTube</span>
 +
                                          <div class="progress">
                                              <div class="bar" style="width: 25%"></div>
                                          </div>
 @@ -112,6 +116,7 @@                                      <li>
                                          Some Download
                                          <span class="pull-right">YouTube</span>
 +
                                          <div class="progress">
                                              <div class="bar" style="width: 45%"></div>
                                          </div>
 @@ -129,23 +134,24 @@              {% endif %}
          </div>
      </header>
 -    <div id="actionbar-container" class="container-fluid">
 +    <div id="content-container" class="container-fluid">
          <div class="row-fluid">
 -        <div class="span2 offset1">
 -            <h5 style="text-align: center">Notifcations or something</h5>
 +            <div class="span2 offset1">
 +                <h5 style="text-align: center">Notifcations or something</h5>
 +            </div>
 +            {% block actionbar %}
 +            {% endblock %}
          </div>
 -        {% block actionbar %}
 -        {% endblock %}
 +        <div class="row-fluid" id="content">
 +{#            TODO: messages #}
 +            {% for msg in messages %}
 +                <p>{{ msg }}</p>
 +            {% endfor %}
 +
 +            {% block content %}
 +            {% endblock content %}
          </div>
      </div>
 -    <div id="content">
 -        {% for msg in messages %}
 -            <p>{{ msg }}</p>
 -        {% endfor %}
 -
 -        {% block content %}
 -        {% endblock content %}
 -    </div>
  </div>
  <footer>
      <div class="center">
 diff --git a/module/web/templates/default/dashboard.html b/module/web/templates/default/dashboard.html index 91a221cea..e512a87d8 100644 --- a/module/web/templates/default/dashboard.html +++ b/module/web/templates/default/dashboard.html @@ -5,7 +5,6 @@  {% block css %}
      <link href="static/css/default/dashboard.less" rel="stylesheet/less" type="text/css" media="screen"/>
 -    <link rel="stylesheet" type="text/css" href="static/css/fontawesome.css" />
  {% endblock %}
  {% block require %}
 @@ -74,98 +73,141 @@  {% endblock %}
  {% block actionbar %}
 -    <ul id="actionbar" class="nav nav-pills span9">
 -    <li>
 -        <ul class="breadcrumb">
 -            <li><a href="#">{{ _("Home") }}</a> <span class="divider">/</span></li>
 -            <li class="active">Data</li>
 -        </ul>
 -    </li>
 +    <ul class="actionbar nav nav-pills span9">
 +        <li>
 +            <ul class="breadcrumb">
 +                <li><a href="#">{{ _("Home") }}</a> <span class="divider">/</span></li>
 +                <li class="active">Data</li>
 +            </ul>
 +        </li>
 -    <li style="float: right;">
 -        <form class="form-search">
 -            <div class="input-append">
 -                <input type="text" class="search-query" style="width: 100px">
 -                <button type="submit" class="btn">{{ _("Search") }}</button>
 -            </div>
 -        </form>
 -    </li>
 -    <li class="dropdown" style="float: right;">
 -        <a class="dropdown-toggle"
 -           data-toggle="dropdown"
 -           href="#">
 -            Type
 -            <b class="caret"></b>
 -        </a>
 -        <ul class="dropdown-menu">
 -            <li><a><i class="icon-ok"></i> Audio</a></li>
 -            <li><a><i class="icon-remove"></i> Video</a></li>
 -            <li><a>Archive</a></li>
 -        </ul>
 -    </li>
 -    <li class="dropdown" style="float: right;">
 -        <a class="dropdown-toggle"
 -           data-toggle="dropdown"
 -           href="#">
 -            More
 -            <b class="caret"></b>
 -        </a>
 -        <ul class="dropdown-menu">
 -            <li><a>Active</a></li>
 -            <li><a>Failed</a></li>
 -        </ul>
 -    </li>
 -
 -    <li style="float: right;">
 -        <a>Failed</a>
 -    </li>
 -    <li style="float: right;">
 -        <a>Unfinished</a>
 -    </li>
 -    <li class="active" style="float: right;">
 -        <a href="#" id="show_active">All</a>
 -    </li>
 +        <li style="float: right;">
 +            <form class="form-search">
 +                <div class="input-append">
 +                    <input type="text" class="search-query" style="width: 100px">
 +                    <button type="submit" class="btn">{{ _("Search") }}</button>
 +                </div>
 +            </form>
 +        </li>
 +        <li class="dropdown" style="float: right;">
 +            <a class="dropdown-toggle"
 +               data-toggle="dropdown"
 +               href="#">
 +                Type
 +                <b class="caret"></b>
 +            </a>
 +            <ul class="dropdown-menu">
 +                <li><a><i class="icon-ok"></i> Audio</a></li>
 +                <li><a><i class="icon-remove"></i> Video</a></li>
 +                <li><a>Archive</a></li>
 +            </ul>
 +        </li>
 +        <li class="dropdown" style="float: right;">
 +            <a class="dropdown-toggle"
 +               data-toggle="dropdown"
 +               href="#">
 +                More
 +                <b class="caret"></b>
 +            </a>
 +            <ul class="dropdown-menu">
 +                <li><a>Active</a></li>
 +                <li><a>Failed</a></li>
 +            </ul>
 +        </li>
 +
 +        <li style="float: right;">
 +            <a>Failed</a>
 +        </li>
 +        <li style="float: right;">
 +            <a>Unfinished</a>
 +        </li>
 +        <li class="active" style="float: right;">
 +            <a href="#" id="show_active">All</a>
 +        </li>
      </ul>
  {% endblock %}
  {% block content %}
 -    <div class="container-fluid">
 -        <div class="row-fluid">
 -            <div class="span3">
 -                <div class="sidebar-header" style="font-size: 2em">
 -                    <i class="iconf-hdd"></i> Packages
 +    <div class="span3">
 +        <div class="sidebar-header">
 +            <i class="iconf-hdd"></i> Packages
 +            <div class="pull-right" style="font-size: medium; line-height: normal">
 +                <i class="iconf-chevron-down" style="font-size: 20px"></i>
 +            </div>
 +            <div class="clearfix"></div>
 +        </div>
 +        <ul class="package-list">
 +            <li class="package-item">
 +                <i class="iconf-folder-close-alt"></i>
 +                Package
 +                {#                        <div class="package-info">#}
 +                {#                            1/2 5MIB 100 MIB#}
 +                {#                        </div>#}
 +                <div class="package-indicator">
 +                    <i class="iconf-check-empty"></i>
 +                    <i class="iconf-chevron-down"></i>
                  </div>
 -                <ul>
 -                    <li>Package</li>
 -                    <li>More package</li>
 -                    <li>many many More package</li>
 -                </ul>
 -                <div class="sidebar-header" style="font-size: 2em">
 -                    <i class="iconf-group"></i> Shared
 +                <div class="progress">
 +                    <div class="bar bar-info" style="width: 50%"></div>
 +                    <div class="bar bar-danger" style="width: 20%"></div>
                  </div>
 -                <ul>
 -                    <li>Content from</li>
 -                    <li>Other user</li>
 -                    <li>which they shared</li>
 -                </ul>
 -                <div class="sidebar-header" style="font-size: 2em">
 -                    <i class="iconf-sitemap"></i> Remote
 +            </li>
 +            <li class="package-item">
 +                <i class="iconf-folder-close-alt"></i>
 +                many many More packages with really long names, some
 +                even don't fit on the screen
 +                <div class="package-indicator">
 +                    <i class="iconf-check-empty"></i>
 +                    <i class="iconf-list"></i>
                  </div>
 -                <ul>
 -                    <li>Content from</li>
 -                    <li>remote sites</li>
 -                    <li>mega</li>
 -                    <li>dropbox</li>
 -                    <li>other pyloads</li>
 -                </ul>
 -            </div>
 -            <div class="span9">
 -                <div id="dashboard">
 -                    {#  Build up by js #}
 +                <div class="progress">
 +                    <div class="bar bar-info" style="width: 50%"></div>
                  </div>
 -            </div>
 +            </li>
 +        </ul>
 +        <div class="sidebar-header">
 +            <i class="iconf-group"></i> Shared
 +        </div>
 +        <ul class="package-list">
 +            <li>Content from</li>
 +            <li>Other user</li>
 +            <li>which they shared</li>
 +        </ul>
 +        <div class="sidebar-header">
 +            <i class="iconf-sitemap"></i> Remote
 +        </div>
 +        <ul>
 +            <li>Content from</li>
 +            <li>remote sites</li>
 +            <li>mega</li>
 +            <li>dropbox</li>
 +            <li>other pyloads</li>
 +        </ul>
 +    </div>
 +    <div class="span9">
 +        <div id="dashboard">
 +            {#  Build up by js #}
          </div>
      </div>
 +
 +    <script src="static/js/libs/jquery-1.9.0.js"></script>
 +    <script src="static/js/libs/select2-3.2.js"></script>
 +    {#    <script src="static/js/libs/jquery.transit-0.9.9.js"></script>#}
 +    <script type="text/javascript">
 +        $("#filter").select2({tags: ["red", "green", "blue"]});
 +
 +        {#        $('.package-indicator').on('mouseenter', function(el) {#}
 +        {#            $(this).parent().transition({#}
 +        {#                rotateX: '180deg'#}
 +        {#            });#}
 +        {#        });#}
 +        {##}
 +        {#        $('.package-item').on('mouseout', function() {#}
 +        {#            $(this).transition({rotateX: '0deg'});#}
 +        {#        })#}
 +
 +    </script>
 +
  {% endblock %}
\ No newline at end of file diff --git a/module/web/templates/default/settings.html b/module/web/templates/default/settings.html index 4c1a3f295..1f9be3db0 100644 --- a/module/web/templates/default/settings.html +++ b/module/web/templates/default/settings.html @@ -28,8 +28,6 @@  {% endblock %}
  {% block content %}
 -    <div class="container-fluid">
 -        <div class="row-fluid">
              <div class="span2">
                  <ul class="nav nav-list well settings-menu">
                  </ul>
 @@ -106,8 +104,6 @@                      </form>
                  </div>
              </div>
 -        </div>
 -    </div>
      <script src="static/js/libs/jquery-1.9.0.js"></script>
      {#    <script src="static/js/libs/bootstrap-2.1.1.js"></script>#}
      <script type="text/javascript">
 | 
