Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
(function() {
var $ = jQuery; // Handle namespaced jQuery
// This is the visual search input that is responsible for creating new facets.
// There is one input placed in between all facets.
VS.ui.SearchInput = Backbone.View.extend({
type : 'text',
className : 'search_input ui-menu',
events : {
'keypress input' : 'keypress',
'keydown input' : 'keydown',
'click input' : 'maybeTripleClick',
'dblclick input' : 'startTripleClickTimer'
},
initialize : function() {
this.app = this.options.app;
this.flags = {
canClose : false
};
_.bindAll(this, 'removeFocus', 'addFocus', 'moveAutocomplete', 'deferDisableEdit');
},
// Rendering the input sets up autocomplete, events on focusing and blurring
// the input, and the auto-grow of the input.
render : function() {
$(this.el).html(JST['search_input']({}));
this.setMode('not', 'editing');
this.setMode('not', 'selected');
this.box = this.$('input');
this.box.autoGrowInput();
this.box.bind('updated.autogrow', this.moveAutocomplete);
this.box.bind('blur', this.deferDisableEdit);
this.box.bind('focus', this.addFocus);
this.setupAutocomplete();
return this;
},
// Watches the input and presents an autocompleted menu, taking the
// remainder of the input field and adding a separate facet for it.
//
// See `addTextFacetRemainder` for explanation on how the remainder works.
setupAutocomplete : function() {
this.box.autocomplete({
minLength : this.options.showFacets ? 0 : 1,
delay : 50,
autoFocus : true,
position : {offset : "0 -1"},
source : _.bind(this.autocompleteValues, this),
create : _.bind(function(e, ui) {
$(this.el).find('.ui-autocomplete-input').css('z-index','auto');
}, this),
select : _.bind(function(e, ui) {
e.preventDefault();
// stopPropogation does weird things in jquery-ui 1.9
// e.stopPropagation();
var remainder = this.addTextFacetRemainder(ui.item.value);
var position = this.options.position + (remainder ? 1 : 0);
this.app.searchBox.addFacet(ui.item instanceof String ? ui.item : ui.item.value, '', position);
return false;
}, this)
});
// Renders the results grouped by the categories they belong to.
this.box.data('uiAutocomplete')._renderMenu = function(ul, items) {
var category = '';
_.each(items, _.bind(function(item, i) {
if (item.category && item.category != category) {
ul.append('
'+item.category+'
');
category = item.category;
}
if(this._renderItemData) {
this._renderItemData(ul, item);
} else {
this._renderItem(ul, item);
}
}, this));
};
this.box.autocomplete('widget').addClass('VS-interface');
},
// Search terms used in the autocomplete menu. The values are matched on the
// first letter of any word in matches, and finally sorted according to the
// value's own category. You can pass `preserveOrder` as an option in the
// `facetMatches` callback to skip any further ordering done client-side.
autocompleteValues : function(req, resp) {
var searchTerm = req.term;
var lastWord = searchTerm.match(/\w+\*?$/); // Autocomplete only last word.
var re = VS.utils.inflector.escapeRegExp(lastWord && lastWord[0] || '');
this.app.options.callbacks.facetMatches(function(prefixes, options) {
options = options || {};
prefixes = prefixes || [];
// Only match from the beginning of the word.
var matcher = new RegExp('^' + re, 'i');
var matches = $.grep(prefixes, function(item) {
return item && matcher.test(item.label || item);
});
if (options.preserveOrder) {
resp(matches);
} else {
resp(_.sortBy(matches, function(match) {
if (match.label) return match.category + '-' + match.label;
else return match;
}));
}
});
},
// Closes the autocomplete menu. Called on disabling, selecting, deselecting,
// and anything else that takes focus out of the facet's input field.
closeAutocomplete : function() {
var autocomplete = this.box.data('uiAutocomplete');
if (autocomplete) autocomplete.close();
},
// As the input field grows, it may move to the next line in the
// search box. `autoGrowInput` triggers an `updated` event on the input
// field, which is bound to this method to move the autocomplete menu.
moveAutocomplete : function() {
var autocomplete = this.box.data('uiAutocomplete');
if (autocomplete) {
autocomplete.menu.element.position({
my : "left top",
at : "left bottom",
of : this.box.data('uiAutocomplete').element,
collision : "none",
offset : '0 -1'
});
}
},
// When a user enters a facet and it is being edited, immediately show
// the autocomplete menu and size it to match the contents.
searchAutocomplete : function(e) {
var autocomplete = this.box.data('uiAutocomplete');
if (autocomplete) {
var menu = autocomplete.menu.element;
autocomplete.search();
// Resize the menu based on the correctly measured width of what's bigger:
// the menu's original size or the menu items' new size.
menu.outerWidth(Math.max(
menu.width('').outerWidth(),
autocomplete.element.outerWidth()
));
}
},
// If a user searches for "word word category", the category would be
// matched and autocompleted, and when selected, the "word word" would
// also be caught as the remainder and then added in its own facet.
addTextFacetRemainder : function(facetValue) {
var boxValue = this.box.val();
var lastWord = boxValue.match(/\b(\w+)$/);
if (!lastWord) {
return '';
}
var matcher = new RegExp(lastWord[0], "i");
if (facetValue.search(matcher) == 0) {
boxValue = boxValue.replace(/\b(\w+)$/, '');
}
boxValue = boxValue.replace('^\s+|\s+$', '');
if (boxValue) {
this.app.searchBox.addFacet(this.app.options.remainder, boxValue, this.options.position);
}
return boxValue;
},
// Directly called to focus the input. This is different from `addFocus`
// because this is not called by a focus event. This instead calls a
// focus event causing the input to become focused.
enableEdit : function(selectText) {
this.addFocus();
if (selectText) {
this.selectText();
}
this.box.focus();
},
// Event called on user focus on the input. Tells all other input and facets
// to give up focus, and starts revving the autocomplete.
addFocus : function() {
this.flags.canClose = false;
if (!this.app.searchBox.allSelected()) {
this.app.searchBox.disableFacets(this);
}
this.app.searchBox.addFocus();
this.setMode('is', 'editing');
this.setMode('not', 'selected');
if (!this.app.searchBox.allSelected()) {
this.searchAutocomplete();
}
},
// Directly called to blur the input. This is different from `removeFocus`
// because this is not called by a blur event.
disableEdit : function() {
this.box.blur();
this.removeFocus();
},
// Event called when user blur's the input, either through the keyboard tabbing
// away or the mouse clicking off. Cleans up
removeFocus : function() {
this.flags.canClose = false;
this.app.searchBox.removeFocus();
this.setMode('not', 'editing');
this.setMode('not', 'selected');
this.closeAutocomplete();
},
// When the user blurs the input, they may either be going to another input
// or off the search box entirely. If they go to another input, this facet
// will be instantly disabled, and the canClose flag will be turned back off.
//
// However, if the user clicks elsewhere on the page, this method starts a timer
// that checks if any of the other inputs are selected or are being edited. If
// not, then it can finally close itself and its autocomplete menu.
deferDisableEdit : function() {
this.flags.canClose = true;
_.delay(_.bind(function() {
if (this.flags.canClose &&
!this.box.is(':focus') &&
this.modes.editing == 'is') {
this.disableEdit();
}
}, this), 250);
},
// Starts a timer that will cause a triple-click, which highlights all facets.
startTripleClickTimer : function() {
this.tripleClickTimer = setTimeout(_.bind(function() {
this.tripleClickTimer = null;
}, this), 500);
},
// Event on click that checks if a triple click is in play. The
// `tripleClickTimer` is counting down, ready to be engaged and intercept
// the click event to force a select all instead.
maybeTripleClick : function(e) {
if (!!this.tripleClickTimer) {
e.preventDefault();
this.app.searchBox.selectAllFacets();
return false;
}
},
// Is the user currently focused in the input field?
isFocused : function() {
return this.box.is(':focus');
},
// When serializing the facets, the inputs need to also have their values represented,
// in case they contain text that is not yet faceted (but will be once the search is
// completed).
value : function() {
return this.box.val();
},
// When switching between facets and inputs, depending on the direction the cursor
// is coming from, the cursor in this facet's input field should match the original
// direction.
setCursorAtEnd : function(direction) {
if (direction == -1) {
this.box.setCursorPosition(this.box.val().length);
} else {
this.box.setCursorPosition(0);
}
},
// Selects the entire range of text in the input. Useful when tabbing between inputs
// and facets.
selectText : function() {
this.box.selectRange(0, this.box.val().length);
if (!this.app.searchBox.allSelected()) {
this.box.focus();
} else {
this.setMode('is', 'selected');
}
},
// Before the searchBox performs a search, we need to close the
// autocomplete menu.
search : function(e, direction) {
if (!direction) direction = 0;
this.closeAutocomplete();
this.app.searchBox.searchEvent(e);
_.defer(_.bind(function() {
this.app.searchBox.focusNextFacet(this, direction);
}, this));
},
// Callback fired on key press in the search box. We search when they hit return.
keypress : function(e) {
var key = VS.app.hotkeys.key(e);
if (key == 'enter') {
return this.search(e, 100);
} else if (VS.app.hotkeys.colon(e)) {
this.box.trigger('resize.autogrow', e);
var query = this.box.val();
var prefixes = [];
if (this.app.options.callbacks.facetMatches) {
this.app.options.callbacks.facetMatches(function(p) {
prefixes = p;
});
}
var labels = _.map(prefixes, function(prefix) {
if (prefix.label) return prefix.label;
else return prefix;
});
if (_.contains(labels, query)) {
e.preventDefault();
var remainder = this.addTextFacetRemainder(query);
var position = this.options.position + (remainder?1:0);
this.app.searchBox.addFacet(query, '', position);
return false;
}
} else if (key == 'backspace') {
if (this.box.getCursorPosition() == 0 && !this.box.getSelection().length) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
this.app.searchBox.resizeFacets();
return false;
}
}
},
// Handles all keyboard inputs when in the input field. This checks
// for movement between facets and inputs, entering a new value that needs
// to be autocompleted, as well as stepping between facets with backspace.
keydown : function(e) {
var key = VS.app.hotkeys.key(e);
if (key == 'left') {
if (this.box.getCursorPosition() == 0) {
e.preventDefault();
this.app.searchBox.focusNextFacet(this, -1, {startAtEnd: -1});
}
} else if (key == 'right') {
if (this.box.getCursorPosition() == this.box.val().length) {
e.preventDefault();
this.app.searchBox.focusNextFacet(this, 1, {selectFacet: true});
}
} else if (VS.app.hotkeys.shift && key == 'tab') {
e.preventDefault();
this.app.searchBox.focusNextFacet(this, -1, {selectText: true});
} else if (key == 'tab') {
var value = this.box.val();
if (value.length) {
e.preventDefault();
var remainder = this.addTextFacetRemainder(value);
var position = this.options.position + (remainder?1:0);
if (value != remainder) {
this.app.searchBox.addFacet(value, '', position);
}
} else {
var foundFacet = this.app.searchBox.focusNextFacet(this, 0, {
skipToFacet: true,
selectText: true
});
if (foundFacet) {
e.preventDefault();
}
}
} else if (VS.app.hotkeys.command &&
String.fromCharCode(e.which).toLowerCase() == 'a') {
e.preventDefault();
this.app.searchBox.selectAllFacets();
return false;
} else if (key == 'backspace' && !this.app.searchBox.allSelected()) {
if (this.box.getCursorPosition() == 0 && !this.box.getSelection().length) {
e.preventDefault();
this.app.searchBox.focusNextFacet(this, -1, {backspace: true});
return false;
}
} else if (key == 'end') {
var view = this.app.searchBox.inputViews[this.app.searchBox.inputViews.length-1];
view.setCursorAtEnd(-1);
} else if (key == 'home') {
var view = this.app.searchBox.inputViews[0];
view.setCursorAtEnd(-1);
}
this.box.trigger('resize.autogrow', e);
}
});
})();