tt-rss/lib/dijit/form/_SearchMixin.js.uncompressed.js
2013-03-18 10:26:26 +04:00

264 řádky
8,3 KiB
JavaScript

define("dijit/form/_SearchMixin", [
"dojo/data/util/filter", // patternToRegExp
"dojo/_base/declare", // declare
"dojo/_base/event", // event.stop
"dojo/keys", // keys
"dojo/_base/lang", // lang.clone lang.hitch
"dojo/query", // query
"dojo/sniff", // has("ie")
"dojo/string", // string.substitute
"dojo/when",
"../registry" // registry.byId
], function(filter, declare, event, keys, lang, query, has, string, when, registry){
// module:
// dijit/form/_SearchMixin
return declare("dijit.form._SearchMixin", null, {
// summary:
// A mixin that implements the base functionality to search a store based upon user-entered text such as
// with `dijit/form/ComboBox` or `dijit/form/FilteringSelect`
// tags:
// protected
// pageSize: Integer
// Argument to data provider.
// Specifies maximum number of search results to return per query
pageSize: Infinity,
// store: [const] dojo/store/api/Store
// Reference to data provider object used by this ComboBox.
// The store must accept an object hash of properties for its query. See `query` and `queryExpr` for details.
store: null,
// fetchProperties: Object
// Mixin to the store's fetch.
// For example, to set the sort order of the ComboBox menu, pass:
// | { sort: [{attribute:"name",descending: true}] }
// To override the default queryOptions so that deep=false, do:
// | { queryOptions: {ignoreCase: true, deep: false} }
fetchProperties:{},
// query: Object
// A query that can be passed to `store` to initially filter the items.
// ComboBox overwrites any reference to the `searchAttr` and sets it to the `queryExpr` with the user's input substituted.
query: {},
// searchDelay: Integer
// Delay in milliseconds between when user types something and we start
// searching based on that value
searchDelay: 200,
// searchAttr: String
// Search for items in the data store where this attribute (in the item)
// matches what the user typed
searchAttr: "name",
// queryExpr: String
// This specifies what query is sent to the data store,
// based on what the user has typed. Changing this expression will modify
// whether the results are only exact matches, a "starting with" match,
// etc.
// dojo.data query expression pattern.
// `${0}` will be substituted for the user text.
// `*` is used for wildcards.
// `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is"
queryExpr: "${0}*",
// ignoreCase: Boolean
// Set true if the query should ignore case when matching possible items
ignoreCase: true,
_abortQuery: function(){
// stop in-progress query
if(this.searchTimer){
this.searchTimer = this.searchTimer.remove();
}
if(this._queryDeferHandle){
this._queryDeferHandle = this._queryDeferHandle.remove();
}
if(this._fetchHandle){
if(this._fetchHandle.abort){
this._cancelingQuery = true;
this._fetchHandle.abort();
this._cancelingQuery = false;
}
if(this._fetchHandle.cancel){
this._cancelingQuery = true;
this._fetchHandle.cancel();
this._cancelingQuery = false;
}
this._fetchHandle = null;
}
},
_processInput: function(/*Event*/ evt){
// summary:
// Handles input (keyboard/paste) events
if(this.disabled || this.readOnly){ return; }
var key = evt.charOrCode;
// except for cutting/pasting case - ctrl + x/v
if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == keys.SHIFT){
return; // throw out weird key combinations and spurious events
}
var doSearch = false;
this._prev_key_backspace = false;
switch(key){
case keys.DELETE:
case keys.BACKSPACE:
this._prev_key_backspace = true;
this._maskValidSubsetError = true;
doSearch = true;
break;
default:
// Non char keys (F1-F12 etc..) shouldn't start a search..
// Ascii characters and IME input (Chinese, Japanese etc.) should.
//IME input produces keycode == 229.
doSearch = typeof key == 'string' || key == 229;
}
if(doSearch){
// need to wait a tad before start search so that the event
// bubbles through DOM and we have value visible
if(!this.store){
this.onSearch();
}else{
this.searchTimer = this.defer("_startSearchFromInput", 1);
}
}
},
onSearch: function(/*===== results, query, options =====*/){
// summary:
// Callback when a search completes.
//
// results: Object
// An array of items from the originating _SearchMixin's store.
//
// query: Object
// A copy of the originating _SearchMixin's query property.
//
// options: Object
// The additional parameters sent to the originating _SearchMixin's store, including: start, count, queryOptions.
//
// tags:
// callback
},
_startSearchFromInput: function(){
this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1"));
},
_startSearch: function(/*String*/ text){
// summary:
// Starts a search for elements matching text (text=="" means to return all items),
// and calls onSearch(...) when the search completes, to display the results.
this._abortQuery();
var
_this = this,
// Setup parameters to be passed to store.query().
// Create a new query to prevent accidentally querying for a hidden
// value from FilteringSelect's keyField
query = lang.clone(this.query), // #5970
options = {
start: 0,
count: this.pageSize,
queryOptions: { // remove for 2.0
ignoreCase: this.ignoreCase,
deep: true
}
},
qs = string.substitute(this.queryExpr, [text]),
q,
startQuery = function(){
var resPromise = _this._fetchHandle = _this.store.query(query, options);
if(_this.disabled || _this.readOnly || (q !== _this._lastQuery)){
return;
} // avoid getting unwanted notify
when(resPromise, function(res){
_this._fetchHandle = null;
if(!_this.disabled && !_this.readOnly && (q === _this._lastQuery)){ // avoid getting unwanted notify
when(resPromise.total, function(total){
res.total = total;
var pageSize = _this.pageSize;
if(isNaN(pageSize) || pageSize > res.total){ pageSize = res.total; }
// Setup method to fetching the next page of results
res.nextPage = function(direction){
// tell callback the direction of the paging so the screen
// reader knows which menu option to shout
options.direction = direction = direction !== false;
options.count = pageSize;
if(direction){
options.start += res.length;
if(options.start >= res.total){
options.count = 0;
}
}else{
options.start -= pageSize;
if(options.start < 0){
options.count = Math.max(pageSize + options.start, 0);
options.start = 0;
}
}
if(options.count <= 0){
res.length = 0;
_this.onSearch(res, query, options);
}else{
startQuery();
}
};
_this.onSearch(res, query, options);
});
}
}, function(err){
_this._fetchHandle = null;
if(!_this._cancelingQuery){ // don't treat canceled query as an error
console.error(_this.declaredClass + ' ' + err.toString());
}
});
};
lang.mixin(options, this.fetchProperties);
// Generate query
if(this.store._oldAPI){
// remove this branch for 2.0
q = qs;
}else{
// Query on searchAttr is a regex for benefit of dojo/store/Memory,
// but with a toString() method to help dojo/store/JsonRest.
// Search string like "Co*" converted to regex like /^Co.*$/i.
q = filter.patternToRegExp(qs, this.ignoreCase);
q.toString = function(){ return qs; };
}
// set _lastQuery, *then* start the timeout
// otherwise, if the user types and the last query returns before the timeout,
// _lastQuery won't be set and their input gets rewritten
this._lastQuery = query[this.searchAttr] = q;
this._queryDeferHandle = this.defer(startQuery, this.searchDelay);
},
//////////// INITIALIZATION METHODS ///////////////////////////////////////
constructor: function(){
this.query={};
this.fetchProperties={};
},
postMixInProperties: function(){
if(!this.store){
var list = this.list;
if(list){
this.store = registry.byId(list);
}
}
this.inherited(arguments);
}
});
});