implemented better files path/tree dropdown using a modified bootstrap-combobox

This commit is contained in:
Gered 2013-05-26 13:49:33 -04:00
parent 55bba64481
commit 0f5302ba1b
4 changed files with 427 additions and 3 deletions

View file

@ -0,0 +1,150 @@
.combobox-container {
margin-bottom: 5px;
*zoom: 1;
}
.combobox-container:before,
.combobox-container:after {
display: table;
content: "";
}
.combobox-container:after {
clear: both;
}
.combobox-container input,
.combobox-container .uneditable-input {
-webkit-border-radius: 0 3px 3px 0;
-moz-border-radius: 0 3px 3px 0;
border-radius: 0 3px 3px 0;
}
.combobox-container input:focus,
.combobox-container .uneditable-input:focus {
position: relative;
z-index: 2;
}
.combobox-container .uneditable-input {
border-left-color: #ccc;
}
.combobox-container .add-on {
float: left;
display: inline-block;
width: auto;
min-width: 16px;
height: inherit !important;
margin-right: -1px;
padding: 4px 5px;
font-weight: normal;
color: #999999;
text-align: center;
text-shadow: 0 1px 0 #ffffff;
background-color: #f5f5f5;
border: 1px solid #ccc;
-webkit-border-radius: 3px 0 0 3px;
-moz-border-radius: 3px 0 0 3px;
border-radius: 3px 0 0 3px;
}
.combobox-container .active {
background-color: #a9dba9;
border-color: #46a546;
}
.combobox-container input,
.combobox-container .uneditable-input {
float: left;
-webkit-border-radius: 3px 0 0 3px;
-moz-border-radius: 3px 0 0 3px;
border-radius: 3px 0 0 3px;
}
.combobox-container .uneditable-input {
border-left-color: #eee;
border-right-color: #ccc;
}
.combobox-container .add-on {
margin-right: 0;
margin-left: -1px;
-webkit-border-radius: 0 3px 3px 0;
-moz-border-radius: 0 3px 3px 0;
border-radius: 0 3px 3px 0;
}
.combobox-container input:first-child {
*margin-left: -160px;
}
.combobox-container input:first-child + .add-on {
*margin-left: -21px;
}
.combobox-container select {
display: inline-block;
width: 0;
height: 0;
border: 0;
padding: 0;
margin: 0;
text-indent: -99999px;
*text-indent: 0;
}
.form-search .combobox-container,
.form-inline .combobox-container {
display: inline-block;
margin-bottom: 0;
vertical-align: top;
}
.form-search .combobox-container .add-on,
.form-inline .combobox-container .add-on {
vertical-align: middle;
}
.combobox-selected .combobox-clear {
/*display: inline-block;*/
}
.combobox-selected .caret {
/*display: none;*/
}
.combobox-clear {
display: none;
width: 14px;
height: 14px;
line-height: 14px;
vertical-align: top;
opacity: 0.3;
filter: alpha(opacity=30);
}
.dropdown:hover .combobox-clear,
.open.dropdown .combobox-clear {
opacity: 1;
filter: alpha(opacity=100);
}
.btn .combobox-clear {
margin-top: 1px;
margin-left: 1px;
}
.btn:hover .combobox-clear,
.open.btn-group .combobox-clear {
opacity: 1;
filter: alpha(opacity=100);
}
.typeahead-long {
max-height: 300px;
overflow-y: auto;
}
.control-group.error .combobox-container .add-on {
color: #B94A48;
border-color: #B94A48;
}
.control-group.error .combobox-container .caret {
border-top-color: #B94A48;
}
.control-group.warning .combobox-container .add-on {
color: #C09853;
border-color: #C09853;
}
.control-group.warning .combobox-container .caret {
border-top-color: #C09853;
}
.control-group.success .combobox-container .add-on {
color: #468847;
border-color: #468847;
}
.control-group.success .combobox-container .caret {
border-top-color: #468847;
}
.btn .combobox-clear [class^="icon-"] {
line-height: 1.4em;
}

View file

@ -0,0 +1,265 @@
/* =============================================================
* bootstrap-combobox.js v1.1.1
* =============================================================
* Copyright 2012 Daniel Farrell
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================ */
!function($) {
"use strict";
var Combobox = function(element, options) {
this.options = $.extend({}, $.fn.combobox.defaults, options)
this.$source = $(element)
this.$container = this.setup()
this.$element = this.$container.find('input[type=text]')
this.$target = this.$container.find('input[type=hidden]')
this.$button = this.$container.find('.dropdown-toggle')
this.$menu = $(this.options.menu).appendTo('body')
this.matcher = this.options.matcher || this.matcher
this.sorter = this.options.sorter || this.sorter
this.highlighter = this.options.highlighter || this.highlighter
this.shown = false
this.selected = false
this.refresh()
this.transferAttributes()
this.listen()
}
/* NOTE: COMBOBOX EXTENDS BOOTSTRAP-TYPEAHEAD.js
========================================== */
Combobox.prototype = $.extend({}, $.fn.typeahead.Constructor.prototype, {
constructor: Combobox,
setup: function() {
var combobox = $(this.options.template)
this.$source.before(combobox)
this.$source.hide()
return combobox
},
parse: function() {
var that = this, map = {}, source = [], selected = false
this.$source.find('option').each(function() {
var option = $(this)
if (option.val() === '') {
that.options.placeholder = option.text()
return
}
map[option.text()] = option.val()
source.push(option.text())
if (option.attr('selected'))
selected = option.html()
})
this.map = map
if (selected) {
this.$element.val(selected)
this.$container.addClass('combobox-selected')
this.selected = true
}
return source
},
transferAttributes: function() {
this.options.placeholder = this.$source.attr('data-placeholder')
|| this.options.placeholder
this.$element.attr('placeholder', this.options.placeholder)
this.$target.prop('name', this.$source.prop('name'))
this.$target.val(this.$source.val())
this.$source.removeAttr('name') // Remove from source otherwise form will pass parameter twice.
this.$element.attr('required', this.$source.attr('required'))
this.$element.attr('rel', this.$source.attr('rel'))
this.$element.attr('title', this.$source.attr('title'))
this.$element.attr('class', this.$source.attr('class'))
},
toggle: function(e) {
if (this.shown) {
this.hide()
} else {
// don't clear the textbox when the arrow is clicked and keep the focus
// inside the textbox to make sure that when the focus leaves (e.g. user
// clicks outside of it) that the dropdown closes as expected
this.lookup(e, '') // pass empty query to show all options (this seems more intuitive to me?)
this.$element.focus()
}
},
clearElement: function() {
this.$element.val('').focus()
},
clearTarget: function() {
this.$source.val('')
this.$target.val('')
this.$container.removeClass('combobox-selected')
this.selected = false
},
triggerChange: function() {
this.$source.trigger('change')
},
refresh: function() {
this.source = this.parse()
this.options.items = this.source.length
},
// modified typeahead function adding container and target handling
select: function() {
var val = this.$menu.find('.active').attr('data-value')
this.$element.val(this.updater(val)).trigger('change')
this.$target.val(this.map[val]).trigger('change')
this.$source.val(this.map[val]).trigger('change')
this.$container.addClass('combobox-selected')
this.selected = true
return this.hide()
},
// modified typeahead function removing the blank handling and source function handling
lookup: function(event, existingVal) {
if (existingVal != undefined)
this.query = existingVal
else
this.query = this.$element.val()
return this.process(this.source)
},
// modified typeahead function adding button handling and remove mouseleave
listen: function() {
this.$element.on('focus', $.proxy(this.focus, this)).on('blur',
$.proxy(this.blur, this)).on('keypress',
$.proxy(this.keypress, this)).on('keyup',
$.proxy(this.keyup, this))
if (this.eventSupported('keydown')) {
this.$element.on('keydown', $.proxy(this.keydown, this))
}
this.$menu.on('click', $.proxy(this.click, this)).on('mouseenter',
'li', $.proxy(this.mouseenter, this)).on('mouseleave',
'li', $.proxy(this.mouseleave, this))
this.$button.on('click', $.proxy(this.toggle, this))
},
// modified typeahead function to clear on type and prevent on moving around
keyup: function(e) {
switch (e.keyCode) {
case 40: // down arrow
case 39: // right arrow
case 38: // up arrow
case 37: // left arrow
case 36: // home
case 35: // end
case 16: // shift
case 17: // ctrl
case 18: // alt
break
case 9: // tab
case 13: // enter
if (!this.shown) {
if (this.options.enter_triggers_change === true)
this.select()
else
return
}
this.select()
break
case 27: // escape
if (!this.shown)
return
this.hide()
break
default:
this.clearTarget()
this.lookup()
}
e.stopPropagation()
e.preventDefault()
},
// modified typeahead function to force a match and add a delay on hide
blur: function(e) {
var that = this
this.focused = false
var val = this.$element.val()
if (!!this.options.force_match) {
if (!this.selected && val !== '') {
this.$element.val('')
this.$target.val('').trigger('change')
this.$source.val('').trigger('change')
}
} else {
this.$target.val(val).trigger('change')
this.$source.val(val).trigger('change')
}
if (!this.mousedover && this.shown)
setTimeout(function() {
that.hide()
}, 200)
},
// modified typeahead function to not hide
mouseleave: function(e) {
this.mousedover = false
},
val: function(val) {
if (val != undefined) {
this.$element.val(val)
this.$target.val(val).trigger('change')
this.$source.val(val).trigger('change')
return val;
} else
return this.$target.val()
}
})
/* COMBOBOX PLUGIN DEFINITION
* =========================== */
$.fn.combobox = function(option) {
return this
.each(function() {
var $this = $(this), data = $this.data('combobox'), options = typeof option == 'object'
&& option
if (!data)
$this.data('combobox', (data = new Combobox(this,
options)))
if (typeof option == 'string')
data[option]()
})
}
$.fn.combobox.defaults = {
template: '<div class="combobox-container"><input type="hidden" /><input type="text" autocomplete="off" /><span class="add-on btn dropdown-toggle" data-dropdown="dropdown"><span class="caret"/><span class="combobox-clear"><i class="icon-remove"/></span></span></div>',
menu: '<ul class="typeahead typeahead-long dropdown-menu"></ul>',
item: '<li><a href="#"></a></li>',
force_match: true,
enter_triggers_change: false
}
$.fn.combobox.Constructor = Combobox
}(window.jQuery);

View file

@ -10,6 +10,7 @@
<link href="{{context}}/css/prettify.css" rel="stylesheet" type="text/css" />
{% if user-id %}
<link href="{{context}}/css/select2.css" rel="stylesheet" type="text/css" />
<link href="{{context}}/css/bootstrap-combobox.css" rel="stylesheet" type="text/css" />
{% endif %}
<link href="{{context}}/css/screen.css" rel="stylesheet" type="text/css" />
@ -24,6 +25,7 @@
<script src="{{context}}/js/prettify.js" type="text/javascript"></script>
{% if user-id %}
<script src="{{context}}/js/select2.min.js" type="text/javascript"></script>
<script src="{{context}}/js/bootstrap-combobox.js" type="text/javascript"></script>
<script src="{{context}}/js/modals.js" type="text/javascript"></script>
{% endif %}
<script src="{{context}}/js/site.js" type="text/javascript"></script>

View file

@ -7,12 +7,13 @@
</div>
<div>
<div class="pull-left">
<div class="pull-left form-inline">
<select id="tree">
{% for folder in tree %}
<option{% ifequal folder path %} selected{% endifequal %}>{{folder}}</option>
{% endfor %}
</select>
<a class="btn" href="#" id="tree-selector">Go</a>
</div>
<div class="pull-right">
<a class="btn btn-primary" href="#" data-toggle="modal" data-target="#newFileModal">Upload File</a>
@ -94,8 +95,14 @@
</div>
<script type="text/javascript">
$('#tree').change(function() {
window.location.href = '{{context}}/listfiles' + $(this).val();
$(document).ready(function() {
$('#tree').combobox({force_match: false});
$('#tree').data('combobox').val('{{path}}');
});
$('#tree-selector').click(function() {
var path = $('#tree').data('combobox').val();
window.location.href = '{{context}}/listfiles' + path;
});
$('a[data-deletefileid]').click(function() {