summaryrefslogtreecommitdiff
path: root/lib/dijit/form/_AutoCompleterMixin.js.uncompressed.js
diff options
context:
space:
mode:
authorAndrew Dolgov <[email protected]>2013-03-18 10:26:24 +0400
committerAndrew Dolgov <[email protected]>2013-03-18 10:26:26 +0400
commitf0cfe83e3725f9a3928da97a6e3085e79cb25309 (patch)
tree4b0af188defaa807c7bc6ff3a101b41c9166c463 /lib/dijit/form/_AutoCompleterMixin.js.uncompressed.js
parent9a2885da170ffd64358b99194095851a2d09c1b6 (diff)
upgrade dojo to 1.8.3 (refs #570)
Diffstat (limited to 'lib/dijit/form/_AutoCompleterMixin.js.uncompressed.js')
-rw-r--r--lib/dijit/form/_AutoCompleterMixin.js.uncompressed.js581
1 files changed, 581 insertions, 0 deletions
diff --git a/lib/dijit/form/_AutoCompleterMixin.js.uncompressed.js b/lib/dijit/form/_AutoCompleterMixin.js.uncompressed.js
new file mode 100644
index 000000000..edbc137a2
--- /dev/null
+++ b/lib/dijit/form/_AutoCompleterMixin.js.uncompressed.js
@@ -0,0 +1,581 @@
+define("dijit/form/_AutoCompleterMixin", [
+ "dojo/data/util/filter", // patternToRegExp
+ "dojo/_base/declare", // declare
+ "dojo/dom-attr", // domAttr.get
+ "dojo/_base/event", // event.stop
+ "dojo/keys",
+ "dojo/_base/lang", // lang.clone lang.hitch
+ "dojo/query", // query
+ "dojo/regexp", // regexp.escapeString
+ "dojo/sniff", // has("ie")
+ "dojo/string", // string.substitute
+ "./DataList",
+ "../registry", // registry.byId
+ "./_TextBoxMixin", // defines _TextBoxMixin.selectInputText
+ "./_SearchMixin"
+], function(filter, declare, domAttr, event, keys, lang, query, regexp, has, string,
+ DataList, registry, _TextBoxMixin, SearchMixin){
+
+ // module:
+ // dijit/form/_AutoCompleterMixin
+
+ return declare("dijit.form._AutoCompleterMixin", SearchMixin, {
+ // summary:
+ // A mixin that implements the base functionality for `dijit/form/ComboBox`/`dijit/form/FilteringSelect`
+ // description:
+ // All widgets that mix in dijit/form/_AutoCompleterMixin must extend `dijit/form/_FormValueWidget`.
+ // tags:
+ // protected
+
+ // item: Object
+ // This is the item returned by the dojo/store/api/Store implementation that
+ // provides the data for this ComboBox, it's the currently selected item.
+ item: null,
+
+ // autoComplete: Boolean
+ // If user types in a partial string, and then tab out of the `<input>` box,
+ // automatically copy the first entry displayed in the drop down list to
+ // the `<input>` field
+ autoComplete: true,
+
+ // highlightMatch: String
+ // One of: "first", "all" or "none".
+ //
+ // If the ComboBox/FilteringSelect opens with the search results and the searched
+ // string can be found, it will be highlighted. If set to "all"
+ // then will probably want to change `queryExpr` parameter to '*${0}*'
+ //
+ // Highlighting is only performed when `labelType` is "text", so as to not
+ // interfere with any HTML markup an HTML label might contain.
+ highlightMatch: "first",
+
+ // labelAttr: String?
+ // The entries in the drop down list come from this attribute in the
+ // dojo.data items.
+ // If not specified, the searchAttr attribute is used instead.
+ labelAttr: "",
+
+ // labelType: String
+ // Specifies how to interpret the labelAttr in the data store items.
+ // Can be "html" or "text".
+ labelType: "text",
+
+ // Flags to _HasDropDown to limit height of drop down to make it fit in viewport
+ maxHeight: -1,
+
+ // For backwards compatibility let onClick events propagate, even clicks on the down arrow button
+ _stopClickEvents: false,
+
+ _getCaretPos: function(/*DomNode*/ element){
+ // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
+ var pos = 0;
+ if(typeof(element.selectionStart) == "number"){
+ // FIXME: this is totally borked on Moz < 1.3. Any recourse?
+ pos = element.selectionStart;
+ }else if(has("ie")){
+ // in the case of a mouse click in a popup being handled,
+ // then the win.doc.selection is not the textarea, but the popup
+ // var r = win.doc.selection.createRange();
+ // hack to get IE 6 to play nice. What a POS browser.
+ var tr = element.ownerDocument.selection.createRange().duplicate();
+ var ntr = element.createTextRange();
+ tr.move("character",0);
+ ntr.move("character",0);
+ try{
+ // If control doesn't have focus, you get an exception.
+ // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes).
+ // There appears to be no workaround for this - googled for quite a while.
+ ntr.setEndPoint("EndToEnd", tr);
+ pos = String(ntr.text).replace(/\r/g,"").length;
+ }catch(e){
+ // If focus has shifted, 0 is fine for caret pos.
+ }
+ }
+ return pos;
+ },
+
+ _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){
+ location = parseInt(location);
+ _TextBoxMixin.selectInputText(element, location, location);
+ },
+
+ _setDisabledAttr: function(/*Boolean*/ value){
+ // Additional code to set disabled state of ComboBox node.
+ // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr().
+ this.inherited(arguments);
+ this.domNode.setAttribute("aria-disabled", value ? "true" : "false");
+ },
+
+ _onKey: function(/*Event*/ evt){
+ // summary:
+ // Handles keyboard events
+
+ if(evt.charCode >= 32){ return; } // alphanumeric reserved for searching
+
+ var key = evt.charCode || evt.keyCode;
+
+ // except for cutting/pasting case - ctrl + x/v
+ if(key == keys.ALT || key == keys.CTRL || key == keys.META || key == keys.SHIFT){
+ return; // throw out spurious events
+ }
+
+ var pw = this.dropDown;
+ var highlighted = null;
+ this._abortQuery();
+
+ // _HasDropDown will do some of the work:
+ //
+ // 1. when drop down is not yet shown:
+ // - if user presses the down arrow key, call loadDropDown()
+ // 2. when drop down is already displayed:
+ // - on ESC key, call closeDropDown()
+ // - otherwise, call dropDown.handleKey() to process the keystroke
+ this.inherited(arguments);
+
+ if(evt.altKey || evt.ctrlKey || evt.metaKey){ return; } // don't process keys with modifiers - but we want shift+TAB
+
+ if(this._opened){
+ highlighted = pw.getHighlightedOption();
+ }
+ switch(key){
+ case keys.PAGE_DOWN:
+ case keys.DOWN_ARROW:
+ case keys.PAGE_UP:
+ case keys.UP_ARROW:
+ // Keystroke caused ComboBox_menu to move to a different item.
+ // Copy new item to <input> box.
+ if(this._opened){
+ this._announceOption(highlighted);
+ }
+ event.stop(evt);
+ break;
+
+ case keys.ENTER:
+ // prevent submitting form if user presses enter. Also
+ // prevent accepting the value if either Next or Previous
+ // are selected
+ if(highlighted){
+ // only stop event on prev/next
+ if(highlighted == pw.nextButton){
+ this._nextSearch(1);
+ event.stop(evt); // prevent submit
+ break;
+ }else if(highlighted == pw.previousButton){
+ this._nextSearch(-1);
+ event.stop(evt); // prevent submit
+ break;
+ }
+ event.stop(evt); // prevent submit if ENTER was to choose an item
+ }else{
+ // Update 'value' (ex: KY) according to currently displayed text
+ this._setBlurValue(); // set value if needed
+ this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting
+ }
+ // fall through
+
+ case keys.TAB:
+ var newvalue = this.get('displayedValue');
+ // if the user had More Choices selected fall into the
+ // _onBlur handler
+ if(pw && (
+ newvalue == pw._messages["previousMessage"] ||
+ newvalue == pw._messages["nextMessage"])
+ ){
+ break;
+ }
+ if(highlighted){
+ this._selectOption(highlighted);
+ }
+ // fall through
+
+ case keys.ESCAPE:
+ if(this._opened){
+ this._lastQuery = null; // in case results come back later
+ this.closeDropDown();
+ }
+ break;
+ }
+ },
+
+ _autoCompleteText: function(/*String*/ text){
+ // summary:
+ // Fill in the textbox with the first item from the drop down
+ // list, and highlight the characters that were
+ // auto-completed. For example, if user typed "CA" and the
+ // drop down list appeared, the textbox would be changed to
+ // "California" and "ifornia" would be highlighted.
+
+ var fn = this.focusNode;
+
+ // IE7: clear selection so next highlight works all the time
+ _TextBoxMixin.selectInputText(fn, fn.value.length);
+ // does text autoComplete the value in the textbox?
+ var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr';
+ if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){
+ var cpos = this.autoComplete ? this._getCaretPos(fn) : fn.value.length;
+ // only try to extend if we added the last character at the end of the input
+ if((cpos+1) > fn.value.length){
+ // only add to input node as we would overwrite Capitalisation of chars
+ // actually, that is ok
+ fn.value = text;//.substr(cpos);
+ // visually highlight the autocompleted characters
+ _TextBoxMixin.selectInputText(fn, cpos);
+ }
+ }else{
+ // text does not autoComplete; replace the whole value and highlight
+ fn.value = text;
+ _TextBoxMixin.selectInputText(fn);
+ }
+ },
+
+ _openResultList: function(/*Object*/ results, /*Object*/ query, /*Object*/ options){
+ // summary:
+ // Callback when a search completes.
+ // description:
+ // 1. generates drop-down list and calls _showResultList() to display it
+ // 2. if this result list is from user pressing "more choices"/"previous choices"
+ // then tell screen reader to announce new option
+ var wasSelected = this.dropDown.getHighlightedOption();
+ this.dropDown.clearResultList();
+ if(!results.length && options.start == 0){ // if no results and not just the previous choices button
+ this.closeDropDown();
+ return;
+ }
+ this._nextSearch = this.dropDown.onPage = lang.hitch(this, function(direction){
+ results.nextPage(direction !== -1);
+ this.focus();
+ });
+
+ // Fill in the textbox with the first item from the drop down list,
+ // and highlight the characters that were auto-completed. For
+ // example, if user typed "CA" and the drop down list appeared, the
+ // textbox would be changed to "California" and "ifornia" would be
+ // highlighted.
+
+ this.dropDown.createOptions(
+ results,
+ options,
+ lang.hitch(this, "_getMenuLabelFromItem")
+ );
+
+ // show our list (only if we have content, else nothing)
+ this._showResultList();
+
+ // #4091:
+ // tell the screen reader that the paging callback finished by
+ // shouting the next choice
+ if("direction" in options){
+ if(options.direction){
+ this.dropDown.highlightFirstOption();
+ }else if(!options.direction){
+ this.dropDown.highlightLastOption();
+ }
+ if(wasSelected){
+ this._announceOption(this.dropDown.getHighlightedOption());
+ }
+ }else if(this.autoComplete && !this._prev_key_backspace
+ // when the user clicks the arrow button to show the full list,
+ // startSearch looks for "*".
+ // it does not make sense to autocomplete
+ // if they are just previewing the options available.
+ && !/^[*]+$/.test(query[this.searchAttr].toString())){
+ this._announceOption(this.dropDown.containerNode.firstChild.nextSibling); // 1st real item
+ }
+ },
+
+ _showResultList: function(){
+ // summary:
+ // Display the drop down if not already displayed, or if it is displayed, then
+ // reposition it if necessary (reposition may be necessary if drop down's height changed).
+ this.closeDropDown(true);
+ this.openDropDown();
+ this.domNode.setAttribute("aria-expanded", "true");
+ },
+
+ loadDropDown: function(/*Function*/ /*===== callback =====*/){
+ // Overrides _HasDropDown.loadDropDown().
+ // This is called when user has pressed button icon or pressed the down arrow key
+ // to open the drop down.
+ this._startSearchAll();
+ },
+
+ isLoaded: function(){
+ // signal to _HasDropDown that it needs to call loadDropDown() to load the
+ // drop down asynchronously before displaying it
+ return false;
+ },
+
+ closeDropDown: function(){
+ // Overrides _HasDropDown.closeDropDown(). Closes the drop down (assuming that it's open).
+ // This method is the callback when the user types ESC or clicking
+ // the button icon while the drop down is open. It's also called by other code.
+ this._abortQuery();
+ if(this._opened){
+ this.inherited(arguments);
+ this.domNode.setAttribute("aria-expanded", "false");
+ this.focusNode.removeAttribute("aria-activedescendant");
+ }
+ },
+
+ _setBlurValue: function(){
+ // if the user clicks away from the textbox OR tabs away, set the
+ // value to the textbox value
+ // #4617:
+ // if value is now more choices or previous choices, revert
+ // the value
+ var newvalue = this.get('displayedValue');
+ var pw = this.dropDown;
+ if(pw && (
+ newvalue == pw._messages["previousMessage"] ||
+ newvalue == pw._messages["nextMessage"]
+ )
+ ){
+ this._setValueAttr(this._lastValueReported, true);
+ }else if(typeof this.item == "undefined"){
+ // Update 'value' (ex: KY) according to currently displayed text
+ this.item = null;
+ this.set('displayedValue', newvalue);
+ }else{
+ if(this.value != this._lastValueReported){
+ this._handleOnChange(this.value, true);
+ }
+ this._refreshState();
+ }
+ },
+
+ _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
+ // summary:
+ // Set the displayed valued in the input box, and the hidden value
+ // that gets submitted, based on a dojo.data store item.
+ // description:
+ // Users shouldn't call this function; they should be calling
+ // set('item', value)
+ // tags:
+ // private
+ var value = '';
+ if(item){
+ if(!displayedValue){
+ displayedValue = this.store._oldAPI ? // remove getValue() for 2.0 (old dojo.data API)
+ this.store.getValue(item, this.searchAttr) : item[this.searchAttr];
+ }
+ value = this._getValueField() != this.searchAttr ? this.store.getIdentity(item) : displayedValue;
+ }
+ this.set('value', value, priorityChange, displayedValue, item);
+ },
+
+ _announceOption: function(/*Node*/ node){
+ // summary:
+ // a11y code that puts the highlighted option in the textbox.
+ // This way screen readers will know what is happening in the
+ // menu.
+
+ if(!node){
+ return;
+ }
+ // pull the text value from the item attached to the DOM node
+ var newValue;
+ if(node == this.dropDown.nextButton ||
+ node == this.dropDown.previousButton){
+ newValue = node.innerHTML;
+ this.item = undefined;
+ this.value = '';
+ }else{
+ var item = this.dropDown.items[node.getAttribute("item")];
+ newValue = (this.store._oldAPI ? // remove getValue() for 2.0 (old dojo.data API)
+ this.store.getValue(item, this.searchAttr) : item[this.searchAttr]).toString();
+ this.set('item', item, false, newValue);
+ }
+ // get the text that the user manually entered (cut off autocompleted text)
+ this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length);
+ // set up ARIA activedescendant
+ this.focusNode.setAttribute("aria-activedescendant", domAttr.get(node, "id"));
+ // autocomplete the rest of the option to announce change
+ this._autoCompleteText(newValue);
+ },
+
+ _selectOption: function(/*DomNode*/ target){
+ // summary:
+ // Menu callback function, called when an item in the menu is selected.
+ this.closeDropDown();
+ if(target){
+ this._announceOption(target);
+ }
+ this._setCaretPos(this.focusNode, this.focusNode.value.length);
+ this._handleOnChange(this.value, true);
+ },
+
+ _startSearchAll: function(){
+ this._startSearch('');
+ },
+
+ _startSearchFromInput: function(){
+ this.item = undefined; // undefined means item needs to be set
+ this.inherited(arguments);
+ },
+
+ _startSearch: function(/*String*/ key){
+ // summary:
+ // Starts a search for elements matching key (key=="" means to return all items),
+ // and calls _openResultList() when the search completes, to display the results.
+ if(!this.dropDown){
+ var popupId = this.id + "_popup",
+ dropDownConstructor = lang.isString(this.dropDownClass) ?
+ lang.getObject(this.dropDownClass, false) : this.dropDownClass;
+ this.dropDown = new dropDownConstructor({
+ onChange: lang.hitch(this, this._selectOption),
+ id: popupId,
+ dir: this.dir,
+ textDir: this.textDir
+ });
+ this.focusNode.removeAttribute("aria-activedescendant");
+ this.textbox.setAttribute("aria-owns",popupId); // associate popup with textbox
+ }
+ this._lastInput = key; // Store exactly what was entered by the user.
+ this.inherited(arguments);
+ },
+
+ _getValueField: function(){
+ // summary:
+ // Helper for postMixInProperties() to set this.value based on data inlined into the markup.
+ // Returns the attribute name in the item (in dijit/form/_ComboBoxDataStore) to use as the value.
+ return this.searchAttr;
+ },
+
+ //////////// INITIALIZATION METHODS ///////////////////////////////////////
+
+ postMixInProperties: function(){
+ this.inherited(arguments);
+ if(!this.store){
+ var srcNodeRef = this.srcNodeRef;
+ // if user didn't specify store, then assume there are option tags
+ this.store = new DataList({}, srcNodeRef);
+
+ // if there is no value set and there is an option list, set
+ // the value to the first value to be consistent with native Select
+ // Firefox and Safari set value
+ // IE6 and Opera set selectedIndex, which is automatically set
+ // by the selected attribute of an option tag
+ // IE6 does not set value, Opera sets value = selectedIndex
+ if(!("value" in this.params)){
+ var item = (this.item = this.store.fetchSelectedItem());
+ if(item){
+ var valueField = this._getValueField();
+ // remove getValue() for 2.0 (old dojo.data API)
+ this.value = this.store._oldAPI ? this.store.getValue(item, valueField) : item[valueField];
+ }
+ }
+ }
+ },
+
+ postCreate: function(){
+ // summary:
+ // Subclasses must call this method from their postCreate() methods
+ // tags:
+ // protected
+
+ // find any associated label element and add to ComboBox node.
+ var label=query('label[for="'+this.id+'"]');
+ if(label.length){
+ if(!label[0].id){ label[0].id = this.id + "_label"; }
+ this.domNode.setAttribute("aria-labelledby", label[0].id);
+
+ }
+ this.inherited(arguments);
+ this.connect(this, "onSearch", "_openResultList");
+ },
+
+ _getMenuLabelFromItem: function(/*Item*/ item){
+ var label = this.labelFunc(item, this.store),
+ labelType = this.labelType;
+ // If labelType is not "text" we don't want to screw any markup ot whatever.
+ if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){
+ label = this.doHighlight(label, this._lastInput);
+ labelType = "html";
+ }
+ return {html: labelType == "html", label: label};
+ },
+
+ doHighlight: function(/*String*/ label, /*String*/ find){
+ // summary:
+ // Highlights the string entered by the user in the menu. By default this
+ // highlights the first occurrence found. Override this method
+ // to implement your custom highlighting.
+ // tags:
+ // protected
+
+ var
+ // Add (g)lobal modifier when this.highlightMatch == "all" and (i)gnorecase when this.ignoreCase == true
+ modifiers = (this.ignoreCase ? "i" : "") + (this.highlightMatch == "all" ? "g" : ""),
+ i = this.queryExpr.indexOf("${0}");
+ find = regexp.escapeString(find); // escape regexp special chars
+ //If < appears in label, and user presses t, we don't want to highlight the t in the escaped "&lt;"
+ //first find out every occurences of "find", wrap each occurence in a pair of "\uFFFF" characters (which
+ //should not appear in any string). then html escape the whole string, and replace '\uFFFF" with the
+ //HTML highlight markup.
+ return this._escapeHtml(label.replace(
+ new RegExp((i == 0 ? "^" : "") + "("+ find +")" + (i == (this.queryExpr.length - 4) ? "$" : ""), modifiers),
+ '\uFFFF$1\uFFFF')).replace(
+ /\uFFFF([^\uFFFF]+)\uFFFF/g, '<span class="dijitComboBoxHighlightMatch">$1</span>'
+ ); // returns String, (almost) valid HTML (entities encoded)
+ },
+
+ _escapeHtml: function(/*String*/ str){
+ // TODO Should become dojo.html.entities(), when exists use instead
+ // summary:
+ // Adds escape sequences for special characters in XML: `&<>"'`
+ str = String(str).replace(/&/gm, "&amp;").replace(/</gm, "&lt;")
+ .replace(/>/gm, "&gt;").replace(/"/gm, "&quot;"); //balance"
+ return str; // string
+ },
+
+ reset: function(){
+ // Overrides the _FormWidget.reset().
+ // Additionally reset the .item (to clean up).
+ this.item = null;
+ this.inherited(arguments);
+ },
+
+ labelFunc: function(item, store){
+ // summary:
+ // Computes the label to display based on the dojo.data store item.
+ // item: Object
+ // The item from the store
+ // store: dojo/store/api/Store
+ // The store.
+ // returns:
+ // The label that the ComboBox should display
+ // tags:
+ // private
+
+ // Use toString() because XMLStore returns an XMLItem whereas this
+ // method is expected to return a String (#9354).
+ // Remove getValue() for 2.0 (old dojo.data API)
+ return (store._oldAPI ? store.getValue(item, this.labelAttr || this.searchAttr) :
+ item[this.labelAttr || this.searchAttr]).toString(); // String
+ },
+
+ _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue, /*item?*/ item){
+ // summary:
+ // Hook so set('value', value) works.
+ // description:
+ // Sets the value of the select.
+ this._set("item", item||null); // value not looked up in store
+ if(value == null /* or undefined */){ value = ''; } // null translates to blank
+ this.inherited(arguments);
+ },
+ _setTextDirAttr: function(/*String*/ textDir){
+ // summary:
+ // Setter for textDir, needed for the dropDown's textDir update.
+ // description:
+ // Users shouldn't call this function; they should be calling
+ // set('textDir', value)
+ // tags:
+ // private
+ this.inherited(arguments);
+ // update the drop down also (_ComboBoxMenuMixin)
+ if(this.dropDown){
+ this.dropDown._set("textDir", textDir);
+ }
+ }
+ });
+});