diff options
Diffstat (limited to 'lib/dijit/form/_SearchMixin.js.uncompressed.js')
-rw-r--r-- | lib/dijit/form/_SearchMixin.js.uncompressed.js | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/lib/dijit/form/_SearchMixin.js.uncompressed.js b/lib/dijit/form/_SearchMixin.js.uncompressed.js new file mode 100644 index 000000000..9cd69fcf7 --- /dev/null +++ b/lib/dijit/form/_SearchMixin.js.uncompressed.js @@ -0,0 +1,264 @@ +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); + } + }); +}); |