UNPKG

dijit

Version:

Dijit provides a complete collection of user interface controls based on Dojo, giving you the power to create web applications that are highly optimized for usability, performance, internationalization, accessibility, but above all deliver an incredible u

280 lines (250 loc) 9.25 kB
define([ "dojo/_base/declare", // declare "dojo/keys", // keys "dojo/_base/lang", // lang.clone lang.hitch "dojo/query", // query "dojo/string", // string.substitute "dojo/when", "../registry" // registry.byId ], function(declare, keys, lang, query, 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: {}, // list: [const] String // Alternate to specifying a store. Id of a dijit/form/DataList widget. list: "", _setListAttr: function(list){ // Avoid having list applied to the DOM node, since it has native meaning in modern browsers this._set("list", list); }, // 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. // `${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, _patternToRegExp: function(pattern){ // summary: // Helper function to convert a simple pattern to a regular expression for matching. // description: // Returns a regular expression object that conforms to the defined conversion rules. // For example: // // - ca* -> /^ca.*$/ // - *ca* -> /^.*ca.*$/ // - *c\*a* -> /^.*c\*a.*$/ // - *c\*a?* -> /^.*c\*a..*$/ // // and so on. // pattern: string // A simple matching pattern to convert that follows basic rules: // // - * Means match anything, so ca* means match anything starting with ca // - ? Means match single character. So, b?b will match to bob and bab, and so on. // - \ is an escape character. So for example, \* means do not treat * as a match, but literal character *. // // To use a \ as a character in the string, it must be escaped. So in the pattern it should be // represented by \\ to be treated as an ordinary \ character instead of an escape. return new RegExp("^" + pattern.replace(/(\\.)|(\*)|(\?)|\W/g, function(str, literal, star, question){ return star ? ".*" : question ? "." : literal ? literal : "\\" + str; }) + "$", this.ignoreCase ? "mi" : "m"); }, _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; this._prev_key_backspace = false; if (key === keys.DELETE || key === keys.BACKSPACE) { this._prev_key_backspace = true; this._maskValidSubsetError = true; } // 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); }, _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.replace(/([\\\*\?])/g, "\\$1")]), 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 = this._patternToRegExp(qs); 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); } }); });