diff --git a/cps/static/css/typeahead.css b/cps/static/css/typeahead.css new file mode 100644 index 00000000..fde250d6 --- /dev/null +++ b/cps/static/css/typeahead.css @@ -0,0 +1,189 @@ +/* + * typehead.js-bootstrap3.less + * @version 0.2.3 + * https://github.com/hyspace/typeahead.js-bootstrap3.less + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ +.has-warning .twitter-typeahead .tt-input, +.has-warning .twitter-typeahead .tt-hint { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-warning .twitter-typeahead .tt-input:focus, +.has-warning .twitter-typeahead .tt-hint:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; +} +.has-error .twitter-typeahead .tt-input, +.has-error .twitter-typeahead .tt-hint { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-error .twitter-typeahead .tt-input:focus, +.has-error .twitter-typeahead .tt-hint:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; +} +.has-success .twitter-typeahead .tt-input, +.has-success .twitter-typeahead .tt-hint { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-success .twitter-typeahead .tt-input:focus, +.has-success .twitter-typeahead .tt-hint:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; +} +.input-group .twitter-typeahead:first-child .tt-input, +.input-group .twitter-typeahead:first-child .tt-hint { + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} +.input-group .twitter-typeahead:last-child .tt-input, +.input-group .twitter-typeahead:last-child .tt-hint { + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; +} +.input-group.input-group-sm .twitter-typeahead .tt-input, +.input-group.input-group-sm .twitter-typeahead .tt-hint { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group.input-group-sm .twitter-typeahead .tt-input, +select.input-group.input-group-sm .twitter-typeahead .tt-hint { + height: 30px; + line-height: 30px; +} +textarea.input-group.input-group-sm .twitter-typeahead .tt-input, +textarea.input-group.input-group-sm .twitter-typeahead .tt-hint, +select[multiple].input-group.input-group-sm .twitter-typeahead .tt-input, +select[multiple].input-group.input-group-sm .twitter-typeahead .tt-hint { + height: auto; +} +.input-group.input-group-sm .twitter-typeahead:not(:first-child):not(:last-child) .tt-input, +.input-group.input-group-sm .twitter-typeahead:not(:first-child):not(:last-child) .tt-hint { + border-radius: 0; +} +.input-group.input-group-sm .twitter-typeahead:first-child .tt-input, +.input-group.input-group-sm .twitter-typeahead:first-child .tt-hint { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.input-group.input-group-sm .twitter-typeahead:last-child .tt-input, +.input-group.input-group-sm .twitter-typeahead:last-child .tt-hint { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; +} +.input-group.input-group-lg .twitter-typeahead .tt-input, +.input-group.input-group-lg .twitter-typeahead .tt-hint { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +select.input-group.input-group-lg .twitter-typeahead .tt-input, +select.input-group.input-group-lg .twitter-typeahead .tt-hint { + height: 46px; + line-height: 46px; +} +textarea.input-group.input-group-lg .twitter-typeahead .tt-input, +textarea.input-group.input-group-lg .twitter-typeahead .tt-hint, +select[multiple].input-group.input-group-lg .twitter-typeahead .tt-input, +select[multiple].input-group.input-group-lg .twitter-typeahead .tt-hint { + height: auto; +} +.input-group.input-group-lg .twitter-typeahead:not(:first-child):not(:last-child) .tt-input, +.input-group.input-group-lg .twitter-typeahead:not(:first-child):not(:last-child) .tt-hint { + border-radius: 0; +} +.input-group.input-group-lg .twitter-typeahead:first-child .tt-input, +.input-group.input-group-lg .twitter-typeahead:first-child .tt-hint { + border-bottom-left-radius: 6px; + border-top-left-radius: 6px; + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.input-group.input-group-lg .twitter-typeahead:last-child .tt-input, +.input-group.input-group-lg .twitter-typeahead:last-child .tt-hint { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + border-bottom-right-radius: 6px; + border-top-right-radius: 6px; +} +.twitter-typeahead { + width: 100%; +} +.input-group .twitter-typeahead { + display: table-cell !important; + float: left; +} +.twitter-typeahead .tt-hint { + color: #999999; +} +.twitter-typeahead .tt-input { + z-index: 2; +} +.twitter-typeahead .tt-input[disabled], +.twitter-typeahead .tt-input[readonly], +fieldset[disabled] .twitter-typeahead .tt-input { + cursor: not-allowed; + background-color: #eeeeee !important; +} +.tt-dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + min-width: 160px; + width: 100%; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + font-size: 14px; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; + *border-right-width: 2px; + *border-bottom-width: 2px; +} +.tt-dropdown-menu .tt-suggestion { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333333; + white-space: nowrap; +} +.tt-dropdown-menu .tt-suggestion.tt-cursor { + text-decoration: none; + outline: 0; + background-color: #f5f5f5; + color: #262626; +} +.tt-dropdown-menu .tt-suggestion.tt-cursor a { + color: #262626; +} +.tt-dropdown-menu .tt-suggestion p { + margin: 0; +} diff --git a/cps/static/css/typeaheadjs.css b/cps/static/css/typeaheadjs.css deleted file mode 100644 index 64c10736..00000000 --- a/cps/static/css/typeaheadjs.css +++ /dev/null @@ -1,93 +0,0 @@ -span.twitter-typeahead .tt-menu, -span.twitter-typeahead .tt-dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; - list-style: none; - font-size: 14px; - text-align: left; - background-color: #ffffff; - border: 1px solid #cccccc; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 4px; - -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - background-clip: padding-box; -} -span.twitter-typeahead .tt-suggestion { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 1.42857143; - color: #333333; - white-space: nowrap; -} -span.twitter-typeahead .tt-suggestion.tt-cursor, -span.twitter-typeahead .tt-suggestion:hover, -span.twitter-typeahead .tt-suggestion:focus { - color: #ffffff; - text-decoration: none; - outline: 0; - background-color: #337ab7; -} -.input-group.input-group-lg span.twitter-typeahead .form-control { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.3333333; - border-radius: 6px; -} -.input-group.input-group-sm span.twitter-typeahead .form-control { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -span.twitter-typeahead { - width: 100%; -} -.input-group span.twitter-typeahead { - display: block !important; - height: 34px; -} -.input-group span.twitter-typeahead .tt-menu, -.input-group span.twitter-typeahead .tt-dropdown-menu { - top: 32px !important; -} -.input-group span.twitter-typeahead:not(:first-child):not(:last-child) .form-control { - border-radius: 0; -} -.input-group span.twitter-typeahead:first-child .form-control { - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.input-group span.twitter-typeahead:last-child .form-control { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; -} -.input-group.input-group-sm span.twitter-typeahead { - height: 30px; -} -.input-group.input-group-sm span.twitter-typeahead .tt-menu, -.input-group.input-group-sm span.twitter-typeahead .tt-dropdown-menu { - top: 30px !important; -} -.input-group.input-group-lg span.twitter-typeahead { - height: 46px; -} -.input-group.input-group-lg span.twitter-typeahead .tt-menu, -.input-group.input-group-lg span.twitter-typeahead .tt-dropdown-menu { - top: 46px !important; -} diff --git a/cps/static/js/bootstrap3-typeahead.js b/cps/static/js/bootstrap3-typeahead.js deleted file mode 100644 index 936d8937..00000000 --- a/cps/static/js/bootstrap3-typeahead.js +++ /dev/null @@ -1,523 +0,0 @@ -/* ============================================================= - * bootstrap3-typeahead.js v3.1.0 - * https://github.com/bassjobsen/Bootstrap-3-Typeahead - * ============================================================= - * Original written by @mdo and @fat - * ============================================================= - * Copyright 2014 Bass Jobsen @bassjobsen - * - * 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 (root, factory) { - - 'use strict'; - - // CommonJS module is defined - if (typeof module !== 'undefined' && module.exports) { - module.exports = factory(require('jquery')); - } - // AMD module is defined - else if (typeof define === 'function' && define.amd) { - define(['jquery'], function ($) { - return factory ($); - }); - } else { - factory(root.jQuery); - } - -}(this, function ($) { - - 'use strict'; - // jshint laxcomma: true - - - /* TYPEAHEAD PUBLIC CLASS DEFINITION - * ================================= */ - - var Typeahead = function (element, options) { - this.$element = $(element); - this.options = $.extend({}, $.fn.typeahead.defaults, options); - this.matcher = this.options.matcher || this.matcher; - this.sorter = this.options.sorter || this.sorter; - this.select = this.options.select || this.select; - this.autoSelect = typeof this.options.autoSelect == 'boolean' ? this.options.autoSelect : true; - this.highlighter = this.options.highlighter || this.highlighter; - this.render = this.options.render || this.render; - this.updater = this.options.updater || this.updater; - this.displayText = this.options.displayText || this.displayText; - this.source = this.options.source; - this.delay = this.options.delay; - this.$menu = $(this.options.menu); - this.$appendTo = this.options.appendTo ? $(this.options.appendTo) : null; - this.shown = false; - this.listen(); - this.showHintOnFocus = typeof this.options.showHintOnFocus == 'boolean' ? this.options.showHintOnFocus : false; - this.afterSelect = this.options.afterSelect; - this.addItem = false; - }; - - Typeahead.prototype = { - - constructor: Typeahead, - - select: function () { - var val = this.$menu.find('.active').data('value'); - this.$element.data('active', val); - if(this.autoSelect || val) { - var newVal = this.updater(val); - // Updater can be set to any random functions via "options" parameter in constructor above. - // Add null check for cases when updater returns void or undefined. - if (!newVal) { - newVal = ""; - } - this.$element - .val(this.displayText(newVal) || newVal) - .change(); - this.afterSelect(newVal); - } - return this.hide(); - }, - - updater: function (item) { - return item; - }, - - setSource: function (source) { - this.source = source; - }, - - show: function () { - var pos = $.extend({}, this.$element.position(), { - height: this.$element[0].offsetHeight - }), scrollHeight; - - scrollHeight = typeof this.options.scrollHeight == 'function' ? - this.options.scrollHeight.call() : - this.options.scrollHeight; - - var element; - if (this.shown) { - element = this.$menu; - } else if (this.$appendTo) { - element = this.$menu.appendTo(this.$appendTo); - } else { - element = this.$menu.insertAfter(this.$element); - } - element.css({ - top: pos.top + pos.height + scrollHeight - , left: pos.left - }) - .show(); - - this.shown = true; - return this; - }, - - hide: function () { - this.$menu.hide(); - this.shown = false; - return this; - }, - - lookup: function (query) { - var items; - if (typeof(query) != 'undefined' && query !== null) { - this.query = query; - } else { - this.query = this.$element.val() || ''; - } - - if (this.query.length < this.options.minLength && !this.options.showHintOnFocus) { - return this.shown ? this.hide() : this; - } - - var worker = $.proxy(function() { - - if($.isFunction(this.source)) this.source(this.query, $.proxy(this.process, this)); - else if (this.source) { - this.process(this.source); - } - }, this); - - clearTimeout(this.lookupWorker); - this.lookupWorker = setTimeout(worker, this.delay); - }, - - process: function (items) { - var that = this; - - items = $.grep(items, function (item) { - return that.matcher(item); - }); - - items = this.sorter(items); - - if (!items.length && !this.options.addItem) { - return this.shown ? this.hide() : this; - } - - if (items.length > 0) { - this.$element.data('active', items[0]); - } else { - this.$element.data('active', null); - } - - // Add item - if (this.options.addItem){ - items.push(this.options.addItem); - } - - if (this.options.items == 'all') { - return this.render(items).show(); - } else { - return this.render(items.slice(0, this.options.items)).show(); - } - }, - - matcher: function (item) { - var it = this.displayText(item); - return ~it.toLowerCase().indexOf(this.query.toLowerCase()); - }, - - sorter: function (items) { - var beginswith = [] - , caseSensitive = [] - , caseInsensitive = [] - , item; - - while ((item = items.shift())) { - var it = this.displayText(item); - if (!it.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item); - else if (~it.indexOf(this.query)) caseSensitive.push(item); - else caseInsensitive.push(item); - } - - return beginswith.concat(caseSensitive, caseInsensitive); - }, - - highlighter: function (item) { - var html = $('
'); - var query = this.query; - var i = item.toLowerCase().indexOf(query.toLowerCase()); - var len, leftPart, middlePart, rightPart, strong; - len = query.length; - if(len === 0){ - return html.text(item).html(); - } - while (i > -1) { - leftPart = item.substr(0, i); - middlePart = item.substr(i, len); - rightPart = item.substr(i + len); - strong = $('').text(middlePart); - html - .append(document.createTextNode(leftPart)) - .append(strong); - item = rightPart; - i = item.toLowerCase().indexOf(query.toLowerCase()); - } - return html.append(document.createTextNode(item)).html(); - }, - - render: function (items) { - var that = this; - var self = this; - var activeFound = false; - var data = []; - var _category = that.options.separator; - - $.each(items, function (key,value) { - // inject separator - if (key > 0 && value[_category] !== items[key - 1][_category]){ - data.push({ - __type: 'divider' - }); - } - - // inject category header - if (value[_category] && (key === 0 || value[_category] !== items[key - 1][_category])){ - data.push({ - __type: 'category', - name: value[_category] - }); - } - data.push(value); - }); - - items = $(data).map(function (i, item) { - - if ((item.__type || false) == 'category'){ - return $(that.options.headerHtml).text(item.name)[0]; - } - - if ((item.__type || false) == 'divider'){ - return $(that.options.headerDivider)[0]; - } - - var text = self.displayText(item); - i = $(that.options.item).data('value', item); - i.find('a').html(that.highlighter(text, item)); - if (text == self.$element.val()) { - i.addClass('active'); - self.$element.data('active', item); - activeFound = true; - } - return i[0]; - }); - - if (this.autoSelect && !activeFound) { - items.filter(':not(.dropdown-header)').first().addClass('active'); - this.$element.data('active', items.first().data('value')); - } - this.$menu.html(items); - return this; - }, - - displayText: function(item) { - return typeof item !== 'undefined' && typeof item.name != 'undefined' && item.name || item; - }, - - next: function (event) { - var active = this.$menu.find('.active').removeClass('active') - , next = active.next(); - - if (!next.length) { - next = $(this.$menu.find('li')[0]); - } - - next.addClass('active'); - }, - - prev: function (event) { - var active = this.$menu.find('.active').removeClass('active') - , prev = active.prev(); - - if (!prev.length) { - prev = this.$menu.find('li').last(); - } - - prev.addClass('active'); - }, - - listen: function () { - this.$element - .on('focus', $.proxy(this.focus, this)) - .on('blur', $.proxy(this.blur, this)) - .on('keypress', $.proxy(this.keypress, this)) - .on('input', $.proxy(this.input, 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)); - }, - - destroy : function () { - this.$element.data('typeahead',null); - this.$element.data('active',null); - this.$element - .off('focus') - .off('blur') - .off('keypress') - .off('input') - .off('keyup'); - - if (this.eventSupported('keydown')) { - this.$element.off('keydown'); - } - - this.$menu.remove(); - }, - - eventSupported: function(eventName) { - var isSupported = eventName in this.$element; - if (!isSupported) { - this.$element.setAttribute(eventName, 'return;'); - isSupported = typeof this.$element[eventName] === 'function'; - } - return isSupported; - }, - - move: function (e) { - if (!this.shown) return; - - switch(e.keyCode) { - case 9: // tab - case 13: // enter - case 27: // escape - e.preventDefault(); - break; - - case 38: // up arrow - // with the shiftKey (this is actually the left parenthesis) - if (e.shiftKey) return; - e.preventDefault(); - this.prev(); - break; - - case 40: // down arrow - // with the shiftKey (this is actually the right parenthesis) - if (e.shiftKey) return; - e.preventDefault(); - this.next(); - break; - } - }, - - keydown: function (e) { - this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]); - if (!this.shown && e.keyCode == 40) { - this.lookup(); - } else { - this.move(e); - } - }, - - keypress: function (e) { - if (this.suppressKeyPressRepeat) return; - this.move(e); - }, - - input: function (e) { - this.lookup(); - e.preventDefault(); - }, - - keyup: function (e) { - switch(e.keyCode) { - case 40: // down arrow - case 38: // up arrow - case 16: // shift - case 17: // ctrl - case 18: // alt - break; - - case 9: // tab - case 13: // enter - if (!this.shown) return; - this.select(); - break; - - case 27: // escape - if (!this.shown) return; - this.hide(); - break; - } - - e.preventDefault(); - }, - - focus: function (e) { - if (!this.focused) { - this.focused = true; - if (this.options.showHintOnFocus) { - this.lookup(''); - } - } - }, - - blur: function (e) { - this.focused = false; - if (!this.mousedover && this.shown) this.hide(); - }, - - click: function (e) { - e.preventDefault(); - this.select(); - this.$element.focus(); - this.hide(); - }, - - mouseenter: function (e) { - this.mousedover = true; - this.$menu.find('.active').removeClass('active'); - $(e.currentTarget).addClass('active'); - }, - - mouseleave: function (e) { - this.mousedover = false; - if (!this.focused && this.shown) this.hide(); - } - - }; - - - /* TYPEAHEAD PLUGIN DEFINITION - * =========================== */ - - var old = $.fn.typeahead; - - $.fn.typeahead = function (option) { - var arg = arguments; - if (typeof option == 'string' && option == 'getActive') { - return this.data('active'); - } - return this.each(function () { - var $this = $(this) - , data = $this.data('typeahead') - , options = typeof option == 'object' && option; - if (!data) $this.data('typeahead', (data = new Typeahead(this, options))); - if (typeof option == 'string' && data[option]) { - if (arg.length > 1) { - data[option].apply(data, Array.prototype.slice.call(arg ,1)); - } else { - data[option](); - } - } - }); - }; - - $.fn.typeahead.defaults = { - source: [], - items: 8, - menu: ' ', - item: '" + displayFn(context) + "
"; + } + } + function isValidName(str) { + return /^[_a-zA-Z0-9-]+$/.test(str); + } + }(); + var Dropdown = function() { + "use strict"; + function Dropdown(o) { + var that = this, onSuggestionClick, onSuggestionMouseEnter, onSuggestionMouseLeave; + o = o || {}; + if (!o.menu) { + $.error("menu is required"); + } + this.isOpen = false; + this.isEmpty = true; + this.datasets = _.map(o.datasets, initializeDataset); + onSuggestionClick = _.bind(this._onSuggestionClick, this); + onSuggestionMouseEnter = _.bind(this._onSuggestionMouseEnter, this); + onSuggestionMouseLeave = _.bind(this._onSuggestionMouseLeave, this); + this.$menu = $(o.menu).on("click.tt", ".tt-suggestion", onSuggestionClick).on("mouseenter.tt", ".tt-suggestion", onSuggestionMouseEnter).on("mouseleave.tt", ".tt-suggestion", onSuggestionMouseLeave); + _.each(this.datasets, function(dataset) { + that.$menu.append(dataset.getRoot()); + dataset.onSync("rendered", that._onRendered, that); + }); + } + _.mixin(Dropdown.prototype, EventEmitter, { + _onSuggestionClick: function onSuggestionClick($e) { + this.trigger("suggestionClicked", $($e.currentTarget)); + }, + _onSuggestionMouseEnter: function onSuggestionMouseEnter($e) { + this._removeCursor(); + this._setCursor($($e.currentTarget), true); + }, + _onSuggestionMouseLeave: function onSuggestionMouseLeave() { + this._removeCursor(); + }, + _onRendered: function onRendered() { + this.isEmpty = _.every(this.datasets, isDatasetEmpty); + this.isEmpty ? this._hide() : this.isOpen && this._show(); + this.trigger("datasetRendered"); + function isDatasetEmpty(dataset) { + return dataset.isEmpty(); + } + }, + _hide: function() { + this.$menu.hide(); + }, + _show: function() { + this.$menu.css("display", "block"); + }, + _getSuggestions: function getSuggestions() { + return this.$menu.find(".tt-suggestion"); + }, + _getCursor: function getCursor() { + return this.$menu.find(".tt-cursor").first(); + }, + _setCursor: function setCursor($el, silent) { + $el.first().addClass("tt-cursor"); + !silent && this.trigger("cursorMoved"); + }, + _removeCursor: function removeCursor() { + this._getCursor().removeClass("tt-cursor"); + }, + _moveCursor: function moveCursor(increment) { + var $suggestions, $oldCursor, newCursorIndex, $newCursor; + if (!this.isOpen) { + return; + } + $oldCursor = this._getCursor(); + $suggestions = this._getSuggestions(); + this._removeCursor(); + newCursorIndex = $suggestions.index($oldCursor) + increment; + newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1; + if (newCursorIndex === -1) { + this.trigger("cursorRemoved"); + return; + } else if (newCursorIndex < -1) { + newCursorIndex = $suggestions.length - 1; + } + this._setCursor($newCursor = $suggestions.eq(newCursorIndex)); + this._ensureVisible($newCursor); + }, + _ensureVisible: function ensureVisible($el) { + var elTop, elBottom, menuScrollTop, menuHeight; + elTop = $el.position().top; + elBottom = elTop + $el.outerHeight(true); + menuScrollTop = this.$menu.scrollTop(); + menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10); + if (elTop < 0) { + this.$menu.scrollTop(menuScrollTop + elTop); + } else if (menuHeight < elBottom) { + this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); + } + }, + close: function close() { + if (this.isOpen) { + this.isOpen = false; + this._removeCursor(); + this._hide(); + this.trigger("closed"); + } + }, + open: function open() { + if (!this.isOpen) { + this.isOpen = true; + !this.isEmpty && this._show(); + this.trigger("opened"); + } + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$menu.css(dir === "ltr" ? css.ltr : css.rtl); + }, + moveCursorUp: function moveCursorUp() { + this._moveCursor(-1); + }, + moveCursorDown: function moveCursorDown() { + this._moveCursor(+1); + }, + getDatumForSuggestion: function getDatumForSuggestion($el) { + var datum = null; + if ($el.length) { + datum = { + raw: Dataset.extractDatum($el), + value: Dataset.extractValue($el), + datasetName: Dataset.extractDatasetName($el) + }; + } + return datum; + }, + getDatumForCursor: function getDatumForCursor() { + return this.getDatumForSuggestion(this._getCursor().first()); + }, + getDatumForTopSuggestion: function getDatumForTopSuggestion() { + return this.getDatumForSuggestion(this._getSuggestions().first()); + }, + update: function update(query) { + _.each(this.datasets, updateDataset); + function updateDataset(dataset) { + dataset.update(query); + } + }, + empty: function empty() { + _.each(this.datasets, clearDataset); + this.isEmpty = true; + function clearDataset(dataset) { + dataset.clear(); + } + }, + isVisible: function isVisible() { + return this.isOpen && !this.isEmpty; + }, + destroy: function destroy() { + this.$menu.off(".tt"); + this.$menu = null; + _.each(this.datasets, destroyDataset); + function destroyDataset(dataset) { + dataset.destroy(); + } + } + }); + return Dropdown; + function initializeDataset(oDataset) { + return new Dataset(oDataset); + } + }(); + var Typeahead = function() { + "use strict"; + var attrsKey = "ttAttrs"; + function Typeahead(o) { + var $menu, $input, $hint; + o = o || {}; + if (!o.input) { + $.error("missing input"); + } + this.isActivated = false; + this.autoselect = !!o.autoselect; + this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; + this.$node = buildDom(o.input, o.withHint); + $menu = this.$node.find(".tt-dropdown-menu"); + $input = this.$node.find(".tt-input"); + $hint = this.$node.find(".tt-hint"); + $input.on("blur.tt", function($e) { + var active, isActive, hasActive; + active = document.activeElement; + isActive = $menu.is(active); + hasActive = $menu.has(active).length > 0; + if (_.isMsie() && (isActive || hasActive)) { + $e.preventDefault(); + $e.stopImmediatePropagation(); + _.defer(function() { + $input.focus(); + }); + } + }); + $menu.on("mousedown.tt", function($e) { + $e.preventDefault(); + }); + this.eventBus = o.eventBus || new EventBus({ + el: $input + }); + this.dropdown = new Dropdown({ + menu: $menu, + datasets: o.datasets + }).onSync("suggestionClicked", this._onSuggestionClicked, this).onSync("cursorMoved", this._onCursorMoved, this).onSync("cursorRemoved", this._onCursorRemoved, this).onSync("opened", this._onOpened, this).onSync("closed", this._onClosed, this).onAsync("datasetRendered", this._onDatasetRendered, this); + this.input = new Input({ + input: $input, + hint: $hint + }).onSync("focused", this._onFocused, this).onSync("blurred", this._onBlurred, this).onSync("enterKeyed", this._onEnterKeyed, this).onSync("tabKeyed", this._onTabKeyed, this).onSync("escKeyed", this._onEscKeyed, this).onSync("upKeyed", this._onUpKeyed, this).onSync("downKeyed", this._onDownKeyed, this).onSync("leftKeyed", this._onLeftKeyed, this).onSync("rightKeyed", this._onRightKeyed, this).onSync("queryChanged", this._onQueryChanged, this).onSync("whitespaceChanged", this._onWhitespaceChanged, this); + this._setLanguageDirection(); + } + _.mixin(Typeahead.prototype, { + _onSuggestionClicked: function onSuggestionClicked(type, $el) { + var datum; + if (datum = this.dropdown.getDatumForSuggestion($el)) { + this._select(datum); + } + }, + _onCursorMoved: function onCursorMoved() { + var datum = this.dropdown.getDatumForCursor(); + this.input.setInputValue(datum.value, true); + this.eventBus.trigger("cursorchanged", datum.raw, datum.datasetName); + }, + _onCursorRemoved: function onCursorRemoved() { + this.input.resetInputValue(); + this._updateHint(); + }, + _onDatasetRendered: function onDatasetRendered() { + this._updateHint(); + }, + _onOpened: function onOpened() { + this._updateHint(); + this.eventBus.trigger("opened"); + }, + _onClosed: function onClosed() { + this.input.clearHint(); + this.eventBus.trigger("closed"); + }, + _onFocused: function onFocused() { + this.isActivated = true; + this.dropdown.open(); + }, + _onBlurred: function onBlurred() { + this.isActivated = false; + this.dropdown.empty(); + this.dropdown.close(); + }, + _onEnterKeyed: function onEnterKeyed(type, $e) { + var cursorDatum, topSuggestionDatum; + cursorDatum = this.dropdown.getDatumForCursor(); + topSuggestionDatum = this.dropdown.getDatumForTopSuggestion(); + if (cursorDatum) { + this._select(cursorDatum); + $e.preventDefault(); + } else if (this.autoselect && topSuggestionDatum) { + this._select(topSuggestionDatum); + $e.preventDefault(); + } + }, + _onTabKeyed: function onTabKeyed(type, $e) { + var datum; + if (datum = this.dropdown.getDatumForCursor()) { + this._select(datum); + $e.preventDefault(); + } else { + this._autocomplete(true); + } + }, + _onEscKeyed: function onEscKeyed() { + this.dropdown.close(); + this.input.resetInputValue(); + }, + _onUpKeyed: function onUpKeyed() { + var query = this.input.getQuery(); + this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorUp(); + this.dropdown.open(); + }, + _onDownKeyed: function onDownKeyed() { + var query = this.input.getQuery(); + this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorDown(); + this.dropdown.open(); + }, + _onLeftKeyed: function onLeftKeyed() { + this.dir === "rtl" && this._autocomplete(); + }, + _onRightKeyed: function onRightKeyed() { + this.dir === "ltr" && this._autocomplete(); + }, + _onQueryChanged: function onQueryChanged(e, query) { + this.input.clearHintIfInvalid(); + query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.empty(); + this.dropdown.open(); + this._setLanguageDirection(); + }, + _onWhitespaceChanged: function onWhitespaceChanged() { + this._updateHint(); + this.dropdown.open(); + }, + _setLanguageDirection: function setLanguageDirection() { + var dir; + if (this.dir !== (dir = this.input.getLanguageDirection())) { + this.dir = dir; + this.$node.css("direction", dir); + this.dropdown.setLanguageDirection(dir); + } + }, + _updateHint: function updateHint() { + var datum, val, query, escapedQuery, frontMatchRegEx, match; + datum = this.dropdown.getDatumForTopSuggestion(); + if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) { + val = this.input.getInputValue(); + query = Input.normalizeQuery(val); + escapedQuery = _.escapeRegExChars(query); + frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); + match = frontMatchRegEx.exec(datum.value); + match ? this.input.setHint(val + match[1]) : this.input.clearHint(); + } else { + this.input.clearHint(); + } + }, + _autocomplete: function autocomplete(laxCursor) { + var hint, query, isCursorAtEnd, datum; + hint = this.input.getHint(); + query = this.input.getQuery(); + isCursorAtEnd = laxCursor || this.input.isCursorAtEnd(); + if (hint && query !== hint && isCursorAtEnd) { + datum = this.dropdown.getDatumForTopSuggestion(); + datum && this.input.setInputValue(datum.value); + this.eventBus.trigger("autocompleted", datum.raw, datum.datasetName); + } + }, + _select: function select(datum) { + this.input.setQuery(datum.value); + this.input.setInputValue(datum.value, true); + this._setLanguageDirection(); + this.eventBus.trigger("selected", datum.raw, datum.datasetName); + this.dropdown.close(); + _.defer(_.bind(this.dropdown.empty, this.dropdown)); + }, + open: function open() { + this.dropdown.open(); + }, + close: function close() { + this.dropdown.close(); + }, + setVal: function setVal(val) { + val = _.toStr(val); + if (this.isActivated) { + this.input.setInputValue(val); + } else { + this.input.setQuery(val); + this.input.setInputValue(val, true); + } + this._setLanguageDirection(); + }, + getVal: function getVal() { + return this.input.getQuery(); + }, + destroy: function destroy() { + this.input.destroy(); + this.dropdown.destroy(); + destroyDomStructure(this.$node); + this.$node = null; + } + }); + return Typeahead; + function buildDom(input, withHint) { + var $input, $wrapper, $dropdown, $hint; + $input = $(input); + $wrapper = $(html.wrapper).css(css.wrapper); + $dropdown = $(html.dropdown).css(css.dropdown); + $hint = $input.clone().css(css.hint).css(getBackgroundStyles($input)); + $hint.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder required").prop("readonly", true).attr({ + autocomplete: "off", + spellcheck: "false", + tabindex: -1 + }); + $input.data(attrsKey, { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass("tt-input").attr({ + autocomplete: "off", + spellcheck: false + }).css(withHint ? css.input : css.inputWithNoHint); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) {} + return $input.wrap($wrapper).parent().prepend(withHint ? $hint : null).append($dropdown); + } + function getBackgroundStyles($el) { + return { + backgroundAttachment: $el.css("background-attachment"), + backgroundClip: $el.css("background-clip"), + backgroundColor: $el.css("background-color"), + backgroundImage: $el.css("background-image"), + backgroundOrigin: $el.css("background-origin"), + backgroundPosition: $el.css("background-position"), + backgroundRepeat: $el.css("background-repeat"), + backgroundSize: $el.css("background-size") + }; + } + function destroyDomStructure($node) { + var $input = $node.find(".tt-input"); + _.each($input.data(attrsKey), function(val, key) { + _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.detach().removeData(attrsKey).removeClass("tt-input").insertAfter($node); + $node.remove(); + } + }(); + (function() { + "use strict"; + var old, typeaheadKey, methods; + old = $.fn.typeahead; + typeaheadKey = "ttTypeahead"; + methods = { + initialize: function initialize(o, datasets) { + datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); + o = o || {}; + return this.each(attach); + function attach() { + var $input = $(this), eventBus, typeahead; + _.each(datasets, function(d) { + d.highlight = !!o.highlight; + }); + typeahead = new Typeahead({ + input: $input, + eventBus: eventBus = new EventBus({ + el: $input + }), + withHint: _.isUndefined(o.hint) ? true : !!o.hint, + minLength: o.minLength, + autoselect: o.autoselect, + datasets: datasets + }); + $input.data(typeaheadKey, typeahead); + } + }, + open: function open() { + return this.each(openTypeahead); + function openTypeahead() { + var $input = $(this), typeahead; + if (typeahead = $input.data(typeaheadKey)) { + typeahead.open(); + } + } + }, + close: function close() { + return this.each(closeTypeahead); + function closeTypeahead() { + var $input = $(this), typeahead; + if (typeahead = $input.data(typeaheadKey)) { + typeahead.close(); + } + } + }, + val: function val(newVal) { + return !arguments.length ? getVal(this.first()) : this.each(setVal); + function setVal() { + var $input = $(this), typeahead; + if (typeahead = $input.data(typeaheadKey)) { + typeahead.setVal(newVal); + } + } + function getVal($input) { + var typeahead, query; + if (typeahead = $input.data(typeaheadKey)) { + query = typeahead.getVal(); + } + return query; + } + }, + destroy: function destroy() { + return this.each(unattach); + function unattach() { + var $input = $(this), typeahead; + if (typeahead = $input.data(typeaheadKey)) { + typeahead.destroy(); + $input.removeData(typeaheadKey); + } + } + } + }; + $.fn.typeahead = function(method) { + var tts; + if (methods[method] && method !== "initialize") { + tts = this.filter(function() { + return !!$(this).data(typeaheadKey); + }); + return methods[method].apply(tts, [].slice.call(arguments, 1)); + } else { + return methods.initialize.apply(this, arguments); + } + }; + $.fn.typeahead.noConflict = function noConflict() { + $.fn.typeahead = old; + return this; + }; + })(); +})(window.jQuery); \ No newline at end of file diff --git a/cps/templates/detail.html b/cps/templates/detail.html index 6a8aafd3..bcaad48a 100644 --- a/cps/templates/detail.html +++ b/cps/templates/detail.html @@ -11,9 +11,14 @@