From 0f5302ba1b6daf5f4044441da9256d6b4b259df8 Mon Sep 17 00:00:00 2001 From: gered Date: Sun, 26 May 2013 13:49:33 -0400 Subject: [PATCH] implemented better files path/tree dropdown using a modified bootstrap-combobox --- resources/public/css/bootstrap-combobox.css | 150 +++++++++++ resources/public/js/bootstrap-combobox.js | 265 ++++++++++++++++++++ src/blarg/views/templates/base.html | 2 + src/blarg/views/templates/files/list.html | 13 +- 4 files changed, 427 insertions(+), 3 deletions(-) create mode 100644 resources/public/css/bootstrap-combobox.css create mode 100644 resources/public/js/bootstrap-combobox.js diff --git a/resources/public/css/bootstrap-combobox.css b/resources/public/css/bootstrap-combobox.css new file mode 100644 index 0000000..7ad3055 --- /dev/null +++ b/resources/public/css/bootstrap-combobox.css @@ -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; +} \ No newline at end of file diff --git a/resources/public/js/bootstrap-combobox.js b/resources/public/js/bootstrap-combobox.js new file mode 100644 index 0000000..95c430c --- /dev/null +++ b/resources/public/js/bootstrap-combobox.js @@ -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: '
', + menu: '', + item: '
  • ', + force_match: true, + enter_triggers_change: false + } + + $.fn.combobox.Constructor = Combobox + +}(window.jQuery); diff --git a/src/blarg/views/templates/base.html b/src/blarg/views/templates/base.html index 621cf18..31fce77 100644 --- a/src/blarg/views/templates/base.html +++ b/src/blarg/views/templates/base.html @@ -10,6 +10,7 @@ {% if user-id %} + {% endif %} @@ -24,6 +25,7 @@ {% if user-id %} + {% endif %} diff --git a/src/blarg/views/templates/files/list.html b/src/blarg/views/templates/files/list.html index d802d81..c9552f4 100644 --- a/src/blarg/views/templates/files/list.html +++ b/src/blarg/views/templates/files/list.html @@ -7,12 +7,13 @@
    -
    +
    + Go
    Upload File @@ -94,8 +95,14 @@