📄 multicombo.js
字号:
/*
* Ext JS Library 3.0 Pre-alpha
* Copyright(c) 2006-2008, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
Ext.ns('Ext.ux');/** * Ext.ux.MultiCombo */Ext.ux.MultiCombo = Ext.extend(Ext.form.ComboBox, { /** * @cfg {String} overClass [x-grid3-row-over] */ overClass : 'x-grid3-row-over', /** * @cfg {Boolean} enableKeyEvents for typeAhead */ enableKeyEvents: true, /** * @cfg {String} selectedClass [x-grid3-row-selected] */ selectedClass: 'x-grid3-row-selected', /** * @cfg {String} highlightClass The css class applied to rows which are hovered with mouse * selected via key-nav, or highlighted when a text-query matches a single item. */ highlightClass: 'x-grid3-row-over', /** * @cfg {Number} autoSelectKey [44] COMMA Sets the key used to auto-select an auto-suggest * highlighted query. When pressed, the highlighted text-item will be selected as if the user * selected the row with a mouse click. */ autoSelectKey : 44, /** * @cfg {String} allSelectedText Text to display when all items are selected */ allSelectedText : 'All selected', /** * @cfg {Number} maxDisplayRows The maximum number of rows to show before applying vscroll */ maxDisplayRows: null, mode: 'local', triggerAction: 'all', typeAhead: true, // private highlightIndex : null, highlightIndexPrev : null, query : null, /** * @cfg {Array} value CheckboxCombo expresses its value as an array. */ value: [], /** * @cfg {Integer} minChars [0] */ minChars: 0, initComponent : function() { var cls = 'x-combo-list'; // when blurring out of field, ensure that rawValue contains ONLY items contained in Store. this.on('blur', this.validateSelections.createDelegate(this)); // create an auto-select key handler, like *nix-based console [tab] key behaviour this.on('keypress', function(field, ev) { if (ev.getKey() == this.autoSelectKey) { // COMMA this.onAutoSelect(); } },this); this.addEvents( /** * @event initview Fires when Combo#initView is called. * gives plugins a chance to interact with DataView * @author Chris Scott * @param {Combo} this * @param {DataView} dv */ 'initview', 'clearall' ); // when list expands, constrain the height with @cfg maxDisplayRows if (this.maxDisplayRows) { this.on('expand', function(){ var cnt = this.store.getCount(); if (cnt > this.maxDisplayRows) { var children = this.view.getNodes(); var h = 0; for (var n = 0; n < this.maxDisplayRows; n++) { h += Ext.fly(children[n]).getHeight(); } this.maxHeight = h; } }, this, { single: true }); } this.on('beforequery', this.onQuery, this); // Enforce that plugins is an Array. if (typeof(this.plugins) == 'undefined'){ this.plugins = []; } else if (!Ext.isArray(this.plugins)) { this.plugins = [this.plugins]; } var tmp = this.value; // for case where transform is set. Ext.ux.MultiCombo.superclass.initComponent.call(this); if (this.transform) { if (typeof(tmp) == 'undefined') { tmp = []; } this.setValue(tmp); } }, // private onViewClick : function(dv, index, node, ev){ var rec = this.store.getAt(index); this.onSelect(rec, index); this.el.focus(); /* if(doFocus !== false){ this.el.focus(); } */ }, // onTriggerClick, overrides Ext.form.ComboBox#onTriggerClick onTriggerClick: function() { if (this.highlightIndex != -1) { this.clearHighlight(); } this.highlightIndex = -1; if(this.disabled){ return; } if(this.isExpanded()){ this.collapse(); this.el.focus(); }else { this.onFocus({}); if(this.triggerAction == 'all') { this.doQuery(this.getRawValue(), true); var vlen = this.getValue().length, slen = this.view.getSelectedRecords().length; if (vlen != slen || vlen == 0) { this.selectByValue(this.value, true); } } else { this.expand(); this.doQuery(this.getRawValue()); } this.highlightIndex = -1 this.highlightIndexPrev = null; this.selectNext(); this.scrollIntoView(); this.el.focus(); } }, // onQuery, beforequery listener, @return false onQuery : function(qe) { q = qe.query; forceAll = qe.forceAll; if(forceAll === true || (q.length >= this.minChars)){ if(this.lastQuery !== q){ if (typeof(this.lastQuery) != 'undefined') { if (q.match(new RegExp('^'+this.allSelectedText))) { this.query = this.store.data; } else if (this.lastQuery.length > q.length) { var items = q.replace(/\s+/g, '').split(','); if (items[items.length-1].length == 0) { items.pop(); } this.query = this.store.data.filterBy(this.store.createFilterFn(this.displayField, new RegExp('^'+items.join('$|^')+'$', "i"), false, false)); } else { this.query = null; } } this.lastQuery = q; if(this.mode == 'local'){ var raw = this.getRawValue(); if (raw == this.allSelectedText) { } var items = raw.replace(/\s+/g, '').split(','); var last = items.pop(); this.matches = this.store.data.filterBy(this.store.createFilterFn(this.displayField, new RegExp('^'+last, "i"), false, false)).filterBy(this.createTypeAheadFilterFn(items)); if (this.matches.getCount() == 0) { this.clearHighlight(); } if (q.length == 0) { this.view.clearSelections(); this.updateValue([]); } this.onLoad(); } else { this.store.baseParams[this.queryParam] = q; this.store.load({ params: this.getParams(q) }); this.expand(); } }else{ this.selectedIndex = -1; this.onLoad(); } } return false; }, // onLoad, overrides Ext.form.ComboBox#onLoad onLoad : function(){ if(!this.hasFocus){ return; } if(this.store.getCount() > 0){ if (!this.isExpanded()) { this.expand(); this.restrictHeight(); } if(this.lastQuery == this.allQuery){ if(this.editable){ this.el.dom.select(); } }else{ if (this.query != null) { var values = [], indexes = []; this.query.each(function(r){ values.push(r.data[this.valueField]); indexes.push(this.store.indexOf(r)); }, this); this.view.clearSelections(); this.updateValue(values, this.getRawValue()); this.view.select(indexes); } if (this.matches != null) { if (this.matches.getCount() == 1) { this.highlight(this.store.indexOf(this.matches.first())); this.scrollIntoView(); } } else { // @HACK: If store was configured with a proxy, set its mode to local now that its populated with data. // Re-execute the query now. this.mode = 'local'; this.lastQuery = undefined; this.doQuery(this.getRawValue(), true); } if(this.typeAhead && this.lastKey != Ext.EventObject.DOWN && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){ this.taTask.delay(this.typeAheadDelay); } } }else{ this.onEmptyResults(); } }, onSelect : function(record, index) { if (index == -1) { throw new Error('MultiCombo#onSelect did not receive a valid index'); } // select only when user clicks [apply] button if (this.selectOnApply == true) { return; } if (this.fireEvent('beforeselect', this, record, index) !== false) { var text = []; var value = []; var rs = this.view.getSelectedRecords(); for (var n = 0, len = rs.length; n < len; n++) { text.push(rs[n].data[this.displayField]); value.push(rs[n].data[this.valueField]); } this.updateValue(value, (value.length != this.store.getCount()) ? text.join(', ') : this.allSelectedText); var node = this.view.getNode(index); this.innerList.scrollChildIntoView(node, false); this.fireEvent('select', this, record, index); } }, // private onViewOver : function(ev, node){ var t = ev.getTarget(this.view.itemSelector); if (t == null) { return; } this.highlightIndex = this.store.indexOf(this.view.getRecord(t)); this.clearHighlight(); this.highlight(this.highlightIndex); if(this.inKeyMode){ // prevent key nav and mouse over conflicts return;null } return; }, // private onTypeAhead : function(){ if(this.store.getCount() > 0){ this.inKeyMode = false; var raw = this.getRawValue(); var pos = this.getCaretPosition(raw); var items = []; var query = ''; if (pos !== false && pos < raw.length) { items = raw.substr(0, pos).replace(/\s+/g, '').split(','); query = items.pop(); } else { items = raw.replace(/\s+/g, '').split(','); query = items.pop(); } var rs = this.store.data.filterBy(this.store.createFilterFn(this.displayField, new RegExp(query, "i"), false, false)).filterBy(this.createTypeAheadFilterFn(items)); if (rs.getCount() == 1) { var r = rs.first(); var rindex = this.store.indexOf(r) if (!this.view.isSelected(rindex)) { this.typeAheadSelected = true; var selStart = raw.length; var len = items.join(',').length; var selEnd = null; var newValue = r.data[this.displayField]; if (pos !== false && pos < raw.length) { var insertIdx = items.length; var selStart = pos; items = raw.replace(/\s+/g, '').split(','); items.splice(insertIdx, 1, newValue); selEnd = items.slice(0, insertIdx+1).join(', ').length; this.highlight(rindex); this.scrollIntoView(); } else { items.push(newValue); } var len = items.join(',').length; if(selStart != len){ var lastWord = raw.split(',').pop(); if (items.length >1 && lastWord.match(/^\s+/) == null) { selStart++; } this.setRawValue(items.join(', ')); this.selectText(selStart, (selEnd!=null) ? selEnd : this.getRawValue().length); } } } } }, apply : function() { var selected = this.view.getSelectedRecords(); var value = []; for (var n=0,len=selected.length;n<len;n++) { value.push(selected[n].data[this.valueField]); } this.setValue(value); }, getCaretPosition : function(raw) { raw = raw || this.getRawValue(); if(document.selection) { // <-- IE, ugh: http://parentnode.org/javascript/working-with-the-cursor-position/ var range = document.selection.createRange(); //Save the current value. We will need this value later to find out, where the text has been changed var orig = obj.value.replace(/rn/g, "n"); // replace the text range.text = text; // Now get the new content and save it into a temporary variable var actual = tmp = obj.value.replace(/rn/g, "n"); /* Find the first occurance, where the original differs from the actual content. This could be the startposition of our text selection, but it has not to be. Think of the selection "ab" and replacing it with "ac". The first difference would be the "c", while the start position is the "a" */ for(var diff = 0; diff < orig.length; diff++) { if(orig.charAt(diff) != actual.charAt(diff)) break; } /* To get the real start position, we iterate through the string searching for the whole replacement text - "abc", as long as the first difference is not reached. If you do not understand that logic - no blame to you, just copy & paste it ;) */ for(var index = 0, start = 0; tmp.match(text) && (tmp = tmp.replace(text, "")) && index <= diff; index = start + text.length) { start = actual.indexOf(text, index); } } else if(this.el.dom.selectionStart) { // <-- Go the Gecko way return this.el.dom.selectionStart; } else { // Fallback for any other browser return false; } }, onAutoSelect : function() { if (!this.isExpanded()) { var vlen = this.getValue().length, slen = this.view.getSelectedRecords().length; if (vlen != slen || vlen == 0) { this.selectByValue(this.value, true); } } var raw = this.getRawValue(); this.selectText(raw.length, raw.length); var pos = this.getCaretPosition(raw); var word = ''; if (pos !== false && pos < raw.length) { word = Ext.util.Format.trim(raw.substr(0, pos).split(',').pop()); } else { word = Ext.util.Format.trim(raw.split(',').pop()); } var idx = this.store.find(this.displayField, word); if (idx > -1 && !this.view.isSelected(idx)) { var rec = this.store.getAt(idx); this.select(idx); } }, // filters-out already-selected items from type-ahead queries. // eg: if store contains: "betty, barney, bart" and betty is already selected, // when user types "b", only "bart" and "barney" should be returned as possible matches, // since betty is *already* selected createTypeAheadFilterFn : function(items) { var key = this.displayField; return function(rec) { var re = new RegExp(rec.data[key], "i"); var add = true; for (var n=0,len=items.length;n<len;n++) { if (re.test(items[n])) { add = false; break; } } return add; } },
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -