I am creating a rich text editor and I would like to use the same button to link and unlink selections.
document.execCommand('createLink'...) and document.execCommand('unlink'...) allow users to link and unlink window.getSelection().toString().
However, there are no inbuilt methods to determine whether a selection is linked or not in the first place, so my question is: How can you check whether or not a selection is linked?
I have tried using document.queryCommandState('createLink') and document.queryCommandState('unlink'), but both queries always return false, even though, for example, document.queryCommandState('bold') works properly.
I found the following piece of code, which works well enough for the time being, kicking around on SO:
const isLink = () => {
if (window.getSelection().toString !== '') {
const selection = window.getSelection().getRangeAt(0)
if (selection) {
if (selection.startContainer.parentNode.tagName === 'A'
|| selection.endContainer.parentNode.tagName === 'A') {
return [true, selection]
} else { return false }
} else { return false }
}
}
You also may be able to retrieve the link HTML element and pass it to Selection.containsNode()
const linkHtmlElement = document.getElementById('yourId');
// should return true if your linkHtmlElement is selected
window.getSelection().containsNode(linkHtmlElement)
You need to check the anchorNode and focusNode text nodes to see if they are a elements. More details on MDN
function isLink () {
const selection = window.getSelection()
const startA = selection.anchorNode.parentNode.tagName === 'A'
const endA = selection.focusNode.parentNode.tagName === 'A'
return startA || endA
}
Related
I'd like to record all the interactions of the users of a web app so I can anlayze how they do use the application. In the first attempt I just want to store the js path of any element clicked. JSPath is to JSON what XPath is to xml.
On Chromium browsers you can get the js path of an element: just open de dev tools (F12), select the "Elements" tab (first one) and right click a node in the DOM and select Copy > Copy JS Path. You'll get the JS Path of the element in your clipboard, something like:
document.querySelector("#how-to-format > div.s-sidebarwidget--content.d-block > div:nth-child(5) > span:nth-child(4)")
If you pase the above code in the console you'll get a reference to the selected node. It's specially useful if the app contains web components so the JSPath contains the shadowRoot selector to traverse the dom to reach the element:
document.querySelector("#one").shadowRoot.querySelector("div > div")
On the first attempt, I think I can just listen for any click in dom and then record the jspath of the element that got the click
document.addEventListener('click', function (event){
//Get the jspath of the element
});
Is there any easy way to get the jspath of the event's target?
Thanks
After some research, I've not been able to find any lib at npm or github neither a response in stackoverflow that supplies what I did expect, so I decided to implement it. Below I'm pasting a simple typescript module that it does the trick. The hardest thing was to deal with slots.
//last 3 elements are window, document and html nodes. We don't need them
function shouldDismiss(tg): boolean {
return tg === window || tg === document || tg?.nodeName == 'HTML';
}
function isSlot(tg): boolean {
return tg?.nodeName === 'SLOT';
}
function isDocumentFragment(node): boolean {
return node?.nodeName === '#document-fragment';
}
function getNodeId(node) {
return node.id ? `#${node.id}` : ''
}
function getNodeClass(node) {
if (!node?.classList)
return '';
let classes = '';
for (let cssClass of node.classList)
classes += `.${cssClass}`
return classes;
}
function getNodeSelector(node) {
return `${node.localName || node.nodeName}${getNodeId(node)}${getNodeClass(node)}`
}
export function getEventJSPath(event: Event): string {
const path = event.composedPath();
let inSlot = false;
let jsPath = path.reduce((previousValue: string, currentValue: any) => {
if (shouldDismiss(currentValue))
return previousValue;
if (isSlot(currentValue)) {
inSlot = true;
return previousValue
}
if (inSlot) {
if (isDocumentFragment(currentValue))
inSlot = false;
return previousValue;
}
if (isDocumentFragment(currentValue))
return previousValue;
const selector = `.querySelector("${getNodeSelector(currentValue)}")${previousValue}`;
//if parent node is a document fragment we need to query its shadowRoot
return isDocumentFragment(currentValue?.parentNode) ? '.shadowRoot' + selector : selector
}, '');
jsPath = 'document' + jsPath;
//Check success on non production environments
if (process?.env != 'prod') {
try {
const el = eval(jsPath);
if (el != path[0]) {
debugger;
console.error('js path error');
}
}
catch (e) {
debugger;
console.error('js path error: ' + e.toString());
}
}
return jsPath;
}
https://jsfiddle.net/AlexThunders/k8s79zL5/18/
I'm trying to change self typing text from English to Russian when I click option in select:
let engType = [
'Never give up. ',
'You can win. '
]
let rusType = [
'Никогда не сдавайся. ',
'Ты можешь победить. '
]
page is loaded and this function gradually types letters:
function typeCharacters(phrases) {
if(phrases[count] !== undefined && phrases[count] !== null && phrases[count] !== "") {
let allLength = phrases[count].length
setInterval(() => {
if(phrases[count] !== undefined && typePar !== null) {
character = phrases[count].slice(ind,ind+1)
txt = document.createTextNode(character)
typePar.appendChild(txt)
ind++
let typeLength = typePar.textContent.length
if(typeLength === allLength) {
count++
ind = 0
if(phrases[count] !== undefined) {
allLength += phrases[count].length
}
}
}
},100)
}
}
typeCharacters(engType)
It works. But when I merely touch select button without even choosing language, I get nonsense paragraph with mixed letters in one or in both languages within the same paragraph:
function searchLang(choosenLang) {
//if choosen language coincides with one in Object:
if(languages[choosenLang]) {
allDataElements.forEach(element => {
//every property of choosen object/language
for(let x in languages[choosenLang]) {
//compare with element's data attribute
if(element.getAttribute('data') === x) {
//the same attribute changes iinerText in accordance with object
element.innerText = languages[choosenLang][x]
if(languages[choosenLang].changePhrases !== undefined) {
languages[choosenLang].changePhrases(choosenLang)
}
}
}
})
}
}
select.addEventListener('click', () => {
allLangOptions.forEach(option => {
if(option.selected === true) {
let lang = option.value
searchLang(lang)
}
})
})
and the result:
Никeгда не сд.вайся. Тыuможешь по едить. OR
Никогда нe up. айс
However for other html elements select button works right: only when I choose option but not click select itself.
I use changePhrases functions in object to change language in typing paragraph:
let languages = {
en: {
mPheadLIabout: 'About',
mPheadLIprojects: 'Projects',
mPheadLIcontacts: 'Contacts',
changePhrases: function() {
if(typePar !== null) {
typePar.textContent = "";
count = 0
ind = 0
typeCharacters(engType)
}
}
},
ru: {
mPheadLIabout: 'О сайте',
mPheadLIprojects: 'Проекты',
mPheadLIcontacts: 'Контакты',
changePhrases: function() {
if(typePar !== null) {
typePar.textContent = "";
count = 0
ind = 0
typeCharacters(rusType)
}
}
}
}
At first paragraph clears itself, and begins to type from first character as indicated above.
I've tried to use variable reset to stop invoking typing English characters but unsuccessfully.
Also I've applied different variants with setTimeout and promise for case when paragraph is cleared and only then you run function typeCharacters(rusType). Still not working and no errors in console.
And the same result I get with English.
Looks like when I click select button(not options) it again fires function to type text, not waits till I use options. And when I click option it fires to times simultaneously.
Here is the entire code and weird result:
https://jsfiddle.net/AlexThunders/k8s79zL5/18/
Instead of binding an event handler to the click event, you should only fire the callback when a user changes the selected option of a select element. So the event type should be change:
select.addEventListener('change', () => {
allLangOptions.forEach((option) => {
if (option.selected === true) {
let lang = option.value;
searchLang(lang);
}
});
});
This way you can also use your keyboard to change the options. Although keyboard users could change the language option, as this is not a mouse click, your click event would not fire and the callback inside your addEventListener would not run. Make sure you always hook to the change event of the select element.
Another problem is that you never cancel the interval. When you select a language, it just starts another timer which just messes up everything. It will look like the effect is sped up, the text become gibberish as the different characters are getting mixed, etc.
To avoid this, you need to save the interval ID returned from window.setInterval() and cancel it when you run typeCharacters():
// ...
let reset = false;
// create a variable in an outer scope
let activeInterval = null;
function typeCharacters(phrases) {
// clear running interval
clearInterval(activeInterval);
if (reset) return;
if(phrases[count] !== undefined
&& phrases[count] !== null
&& phrases[count] !== "") {
let allLength = phrases[count].length
// save the interval ID
activeInterval = setInterval(() => {
if(phrases[count] !== undefined && typePar !== null) {
// ... rest of your code
I have a settings panel on which there is a checkbox that is responsible for auto-theme change on off. The first problem is that this checkbox does not allow to be changed(I see a function triggered). The second problem Is that I want on every reload the value (true or false) to be returned and set to the checkbox. How to fix this issues?
TimeSch(localStorage.getItem("'TimeSch'"));
function TimeSch(a) {
if (a) {
document.getElementById('TimeSch').checked = 'true';
console.log('True start');
Set('TimeSch', true);
$('#theme').prop('disabled', true);
const hours = new Date().getHours();
const isDayTime = hours > 6 && hours < 20;
if (isDayTime) {
theme('White');
console.log('Auto White');
} else {
theme('Dark');
console.log('Auto Dark');
}
} else {
document.getElementById('TimeSch').checked = 'false';
console.log('False start');
$('#theme').prop('disabled', false);
Set('TimeSch', false);
}
}
function Set(name, value) {
if (typeof Storage !== "undefined") {
localStorage.setItem("'" + name + "'", value);
} else {
console.log('No localStorage supported. Oh, please chage your browser!');
}
}
https://codepen.io/abooo/pen/PVQaWB
First thing, on line 4 and line 18 use boolean
document.getElementById('TimeSch').checked = true;
Secondly, you don't need to add quotes for localStorage keys. Simply do:
localStorage.setItem(name, value);
localStorage.getItem("TimeSch")
Finally the problem is line 3: The value coming from localStorage is string, so the condition will always be true. Change this to:
if (a === "true" || a === true)
and it should work.
Link to these changes: https://codepen.io/anon/pen/dadjGq?editors=1111
There are multiple issues with your code snippet:
there's no implementation of the theme() function
input[type="checkbox"] is inside of the select element, which is invalid markup, also they get enabled/disabled simultaneously
css for input[type=checkbox]:after doesn't take into account the state of the checkbox, so it always will show only x
to set the property of the element you do not need jQuery
to check the checkbox through JS you should give it Boolean, not the string
why would you do this: localStorage.setItem("'" + name + "'", ...? consider using: localStorage.setItem(name, ... directly
It is still not clear what you are trying to achieve in general, but I made some patches and at least it got somewhat alive: https://codepen.io/anon/pen/OdQEvr
I have a script here that interacts with a form. It works fine but has a couple of hiccups before we can call it complete.
The script asks multiple questions which responds depending on what the user selects.
The order of questions are as follows:
Name, Bedrooms, Stories, Style.
I have two problems.
1) You can skip the first input field without entering a name. I need to make sure the user enters a name.
2) I get an invalid response when I go from Stories > Styles. Why?
If I could get some help that would be great.
Cheers
window.addEventListener('load', function()
{
// Create our node store, allowing us easy access to the nodes in the future.
var nodes = {};
nodes.consultant = document.querySelector('#consultant');
nodes.scope = nodes.consultant.querySelector('.col');
nodes.response = nodes.scope.querySelector('.consultant-response');
nodes.question = nodes.scope.querySelector('.consultant-question');
nodes.input = nodes.scope.querySelector('.consultant-input-wrap');
nodes.image = nodes.consultant.querySelector('.consultant-image');
nodes.dots = nodes.scope.querySelector('.consultant-dots');
var clientName = '';
// This variable will store the current question.
var question;
// Create our question store, we will iterate through these by shifting the
// first item off and replacing the question variable with it. Once the
// client has responded to the question, it will be pushed in the responses
// store below.
var questions = [
{
name: 'name',
question: 'Let’s start now. You know my name. What’s yours?',
image: 'consultant-introduction.png',
input: (function()
{
var form = document.createElement('form');
var input = document.createElement('input');
input.placeholder = 'Enter your name';
input.className = 'consultant-input';
form.appendChild(input);
var submit = document.createElement('button');
submit.className = 'fa fa-chevron-right';
form.appendChild(submit);
return form;
})(),
response: function(name)
{
if (typeof name === 'undefined')
{
return;
}
question.image = 'consultant-bedroom-question.png';
clientName = name;
return "Hey " + name + ". Great to meet you. I’ve got a couple of questions about the new home you want to build.";
}
},
{
name: 'bedrooms',
question: "How many bedrooms would you like?",
input: document.querySelector('[name="_sfm_bedrooms[]"]').cloneNode(true),
original: document.querySelector('[name="_sfm_bedrooms[]"]'),
response: function(bedrooms)
{
if (typeof bedrooms === 'undefined')
{
return;
}
var response = '';
switch (+bedrooms)
{
case 5:
response = "Five! Sounds like you need plenty of room for the family and a guest or three.";
question.image = 'consultant-five-bedroom-answer.png';
break;
case 4:
response = "4 bedrooms, great! We’ve got plenty of ideas for you already.";
question.image = 'consultant-four-bedroom-answer.png';
break;
case 3:
response = "Brilliant, 3 bedrooms. We love smaller but perfectly formed homes too.";
question.image = 'consultant-three-bedroom-answer.png';
break;
default:
response = "Well that isn't a valid response...";
question.image = '';
}
return response;
}
},
{
name: 'levels',
question: function(bedrooms)
{
if (typeof bedrooms === 'undefined')
{
return;
}
var question = '';
switch (+bedrooms)
{
case 5:
question = "Does that mean you want two storeys as well or would you rather spread out over one?";
break;
case 4:
question = "How high would you like to go? One or two storeys?";
break;
case 3:
question = "Does that mean you’re after a single storey home or two levels maybe? ";
break;
default:
}
return question;
},
input: document.querySelector('[name="_sfm_levels[]"]').cloneNode(true),
original: document.querySelector('[name="_sfm_levels[]"]'),
response: function(levels)
{
var response = '';
switch (+levels)
{
case 2:
response = "Thanks " + clientName + ", two storeys will give you a lot of flexibility.";
question.image = 'consultant-two-storey-answer.png';
break;
case 1:
response = "Nice " + clientName + ", a single storey home is a great option.";
question.image = 'consultant-one-storey-answer.png';
break;
default:
response = "Well that isn't a valid response...";
question.image = '';
}
return response;
}
},
{
name: 'style',
question: "I’ve got one more question, do you like contemporary or traditional style homes?",
image: 'consultant-style-answer.png',
input: document.querySelector('[name="_sfm_style[]"]').cloneNode(true),
original: document.querySelector('[name="_sfm_style[]"]'),
response: function(style)
{
var response = '';
switch (style)
{
case 'Traditional':
response = "You can’t beat the classics eh! Brilliant, check out a few ideas we think you’ll like below.";
break;
case 'Modern':
response = "Great, you’re after something a little more modern. Check out a few ideas we think you’ll like below.";
break;
default:
response = "Well that isn't a valid response...";
question.image = '';
}
return response;
}
},
{
name: 'done',
question: "Remember, these plans are just a starting point. We can completely customise any of them to suit your needs perfectly.",
input: (function()
{
var wrap = document.createElement('div');
wrap.className = 'consultant-done-wrap';
var elmnt = document.createElement('a');
elmnt.className = 'consultant-done fa fa-angle-down';
elmnt.href = "#find-a-plan";
wrap.appendChild(elmnt);
return wrap;
})()
}
];
// Once a client responds to a question, that question object will be added
// to this array.
var responses = [];
// Create our function store, this helps organize our functions, but also
// prevents conflict with existing function names.
var funcs = {};
// Prompt the user with the new question.
funcs.prompt = function(e)
{
// Cancel the default event if an event object is supplied
if (typeof e !== 'undefined' && typeof e.preventDefault === 'function')
{
e.preventDefault();
}
// Store the previous response if it is available, so that we can pass
// it to the question function.
var previous = {};
if (typeof question !== 'undefined')
{
if (question.input instanceof HTMLElement)
{
// If the input field is a form element, we have to search for
// the input element.
if (/form/i.test(question.input.tagName))
{
previous.value = question.input.querySelector('input').value;
}
else
{
previous.value = question.input.value;
}
// If there was no selected value, or the value is empty, stop here
if (typeof previous === 'undefined' || previous.length === 0)
{
return;
}
}
// Check if the response field is supplied
if (typeof question.response !== 'undefined')
{
if (typeof question.response === 'function')
{
// If the response is a function, call it with the value of the input,
// and apply the output to the response node.
nodes.response.innerHTML = question.response(previous.value);
}
else
{
// If it isn't a function, apply the value to the response node.
nodes.response.innerHTML = question.response;
}
// Push the current question onto the response array.
responses.push(question);
// Remove the current input element.
nodes.input.removeChild(question.input);
}
// Just a check to make sure the dot element is actually an element,
// safety first!
if (question.dot instanceof HTMLElement)
{
// Set the dot for the current question as active.
question.dot.className += ' active';
}
previous.question = question;
}
// Assign the new question to the current question variable.
// This removes the first question from the question store.
question = questions.shift();
// If we are on the first question, load the image for this question.
if (typeof previous.question === 'undefined')
{
previous.question = question;
}
// If there is an image attached to the previous question, show it.
// This is usually set in the response field, so we can know what the response was.
if (typeof previous.question.image !== 'undefined')
{
if (nodes.image instanceof HTMLElement)
{
nodes.image.style.backgroundImage = 'url(' + previous.question.image + ')';
}
}
// Check to see if the question field is set.
if (typeof question.question !== 'undefined')
{
if (typeof question.question === 'function')
{
// If the question is a function, call it and apply the output
// to the question node.
nodes.question.innerHTML = question.question(previous.value);
}
else
{
// If it isn't a function, apply the value to the question node.
nodes.question.innerHTML = question.question;
}
}
// Check if the input field is an HTML element
if (question.input instanceof HTMLElement)
{
// If it is, insert it into the input container.
nodes.input.appendChild(question.input);
}
// Check to see if we are on the last question or not.
if (question.name !== 'done')
{
// If not, check if the input element is an HTML element
if (question.input instanceof HTMLElement)
{
// Make sure the input element isn't actually a form element.
if (!/form/i.test(question.input.tagName))
{
// Apply the styling to the input element.
question.input.className += ' consultant-input';
// Apply the event listener to the input element.
question.input.addEventListener('change', funcs.prompt, false);
}
else
{
// Apply the event listener to the input element.
question.input.addEventListener('submit', funcs.prompt, false);
}
}
}
else
{
// If we are on the last question, remove the dots and update the originals.
nodes.scope.removeChild(nodes.dots);
funcs.updateAllInputs();
}
};
// Update an individual input.
funcs.updateInput = function(response)
{
// If no original element was passed, exit the function
if (typeof response.original === 'undefined')
{
return;
}
// Check to see if the original field is actually an HTML element.
if (response.original instanceof HTMLElement)
{
// Set the value of the original input to that of the client input.
response.original.value = response.input.value;
// This is because IE is stupid. We have to check if "fireEvent" is a function.
if ("fireEvent" in document)
{
// If it is, the user is browsing with IE, use the "fireEvent" function
response.original.fireEvent('onchange');
}
else
{
// If it isn't, the user is browsing with a real browser.
// Create a "change" event.
var event = new UIEvent("change",
{
"view": window,
"bubbles": true,
"cancelable": true
});
// Dispatch the "change" event
response.original.dispatchEvent(event);
}
}
};
// Update all of the inputs.
funcs.updateAllInputs = function()
{
// Iterate through the responses array.
for (var i in responses)
{
// This excludes the "length" field, only relevant on mobile devices.
if (responses.hasOwnProperty(i))
{
// Update the original input.
funcs.updateInput(responses[i]);
}
}
};
// Make the dots to show the progress through the consultant
funcs.makeDots = function()
{
// Iterate through the questions array.
for (var i in questions)
{
// This excludes the "length" field, only relevant on mobile devices.
if (questions.hasOwnProperty(i))
{
// Skip the last question.
if (questions[i].name === 'done')
{
continue;
}
// Make the dot
questions[i].dot = document.createElement('li');
// Append the dot to the dots container
nodes.dots.appendChild(questions[i].dot);
}
}
};
// Make the dots.
funcs.makeDots();
// Prompt the first question.
funcs.prompt();
}, false);
HTML
<section class="beige section-padding strip-wrap" id="consultant">
<div class="container entry clr">
<div class="clr col span_1_of_3 col-1">
<h1 class="red">Can I help?</h1>
<div class="white consultant-response">
Hi, I'm Glenda. Our job is to make your build as easy and tailored as possible.
</div>
<div class="orange consultant-question"></div>
<div class="consultant-input-wrap"></div>
<ul class="consultant-dots"></ul>
</div>
<div class="clr col span_2_of_3"></div>
<a href="#find-a-plan">
<div class="strip">These homes are a perfect starting point
<div class="strip-arrow"><i class="fa fa-chevron-down"></i></div>
</div>
</a>
</div>
<!-- .entry-content -->
<div class="consultant-showcase">
<div class="consultant-image">
<div class="consultant-introduction"></div>
<div class="consultant-bedroom-question"></div>
<div class="consultant-three-bedroom-answer"></div>
<div class="consultant-four-bedroom-answer"></div>
<div class="consultant-five-bedroom-answer"></div>
<div class="consultant-one-storey-answer"></div>
<div class="consultant-two-storey-aswer"></div>
<div class="consultant-style-answer"></div>
</div>
</div>
</section>
1) You can skip the first input field without entering a name. I need
to make sure the user enters a name.
Use required attribute at input or select elements.
Requesting a sanity check here please...
ExtJS 4.2 comboBox Typeahead works but having issues retrieving the valueField under the following conditions:-
1) If a user types a value and then instead of hitting enter or clicking on the select combo list, they click elsewhere then the valueField is empty but the selected value is present.
2) Assuming that the combobox item was selected correctly, If I enter an additional character and then backspace that character, the combo box can no longer find the valueField..its almost like it has reset itself.
Fiddle example
https://fiddle.sencha.com/#fiddle/je1
How to reproduce
If you enter Maggie in the combo box, you will see the valueField ID in the console window, if you append a character and then backspace the character, the ID in the console window is null
(You will need to open the console window to see the output)
forceSelection does not resolve this issue as I have a template and it will not accept an entry in the combobox that is not part of the store, and I need to use sumID for my valueField as I need to retrieve and pass that value to the server.
Thank you everyone, awesome to have such a great community!!
I was able to get around this by using forceSelection and overriding the setValue thus allowing template items not in the store but in the combo to be selected via forceSelection. From playing around with the combobox, IMO, for a good look and feel, forceSelection is the way to go.
Here is my override, refer to statement //start of override
This was a quick fix, I will refine statement when I am back in the office, below I am pasting the solution from memory, you get the idea.
setValue: function(value, doSelect) {
var me = this,
valueNotFoundText = me.valueNotFoundText,
inputEl = me.inputEl,
i, len, record,
dataObj,
matchedRecords = [],
displayTplData = [],
processedValue = [];
if (me.store.loading) {
// Called while the Store is loading. Ensure it is processed by the onLoad method.
me.value = value;
me.setHiddenValue(me.value);
return me;
}
// This method processes multi-values, so ensure value is an array.
value = Ext.Array.from(value);
// Loop through values, matching each from the Store, and collecting matched records
for (i = 0, len = value.length; i < len; i++) {
record = value[i];
if (!record || !record.isModel) {
record = me.findRecordByValue(record);
}
// record found, select it.
if (record) {
matchedRecords.push(record);
displayTplData.push(record.data);
processedValue.push(record.get(me.valueField));
}
// record was not found, this could happen because
// store is not loaded or they set a value not in the store
else {
//start of override
// 'Select All Names' is the template item that was added // to the combo box, it looks like an entry from the store
// but it is not in the store
if (me.forceSelection && me.getDisplayValue() === 'Select All Names'){
processedValue.push(value[i]);
dataObj = {};
dataObj[me.displayField] = value[i];
displayTplData.push(dataObj);
}
//end of override
if (!me.forceSelection) {
processedValue.push(value[i]);
dataObj = {};
dataObj[me.displayField] = value[i];
displayTplData.push(dataObj);
// TODO: Add config to create new records on selection of a value that has no match in the Store
}
// Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
else if (Ext.isDefined(valueNotFoundText)) {
displayTplData.push(valueNotFoundText);
}
}
}
// Set the value of this field. If we are multiselecting, then that is an array.
me.setHiddenValue(processedValue);
me.value = me.multiSelect ? processedValue : processedValue[0];
if (!Ext.isDefined(me.value)) {
me.value = null;
}
me.displayTplData = displayTplData; //store for getDisplayValue method
me.lastSelection = me.valueModels = matchedRecords;
if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
inputEl.removeCls(me.emptyCls);
}
// Calculate raw value from the collection of Model data
me.setRawValue(me.getDisplayValue());
me.checkChange();
if (doSelect !== false) {
me.syncSelection();
}
me.applyEmptyText();
return me;
},
Look at the sources of Combobox and try override this method as follows
doLocalQuery: function(queryPlan) {
var me = this,
queryString = queryPlan.query;
if (!me.queryFilter) {
me.queryFilter = new Ext.util.Filter({
id: me.id + '-query-filter',
anyMatch: me.anyMatch,
caseSensitive: me.caseSensitive,
root: 'data',
property: me.displayField
});
me.store.addFilter(me.queryFilter, false);
}
if (queryString || !queryPlan.forceAll) {
me.queryFilter.disabled = false;
me.queryFilter.setValue(me.enableRegEx ? new RegExp(queryString) : queryString);
}
else {
me.queryFilter.disabled = true;
}
me.store.filter();
if (me.store.getCount()) {
if (me.rawValue === me.lastSelection[0].get(me.displayField)){
me.setValue(me.lastSelection);
} else {
if(me.store.getCount() === 1){
me.setValue(me.store.first());
}
me.expand();
}
} else {
me.collapse();
}
me.afterQuery(queryPlan);
},