I am looking to use the component query function to find components not by attribute value, but by attribute name where the attribute name itself should be a wildcard.
So I have a component with the following config:
var component = Ext.create({
xtype: 'container',
foobar: 'something',
foobar2: 'something else'
});
I want to be able to do find all elements that have the attribute starting with foobar. Something like
var els = Ext.ComponentQuery.query('[foobar*]');
How would I go about achieving that? Is this an option?
I didn't find a default way in the documentation. What would a possible solution is a custom matcher function that checks each component for properties that start with given parameter. You could then make an override for all ext components to have this matcher function. (If you're already using custom components you could make a mixin)
For Example:
Ext.override(Ext.Component, {
insensetivePropQuery: function(prop) {
var matched = false;
Ext.Object.each(this, function(key, value, myself) {
if(Ext.String.startsWith(key, prop) === true) {
matched = true;
return false;
}
});
return matched;
}
});
Query example:
Ext.ComponentQuery.query('{insensetivePropQuery("foobar")}');
Here is a working sencha fiddle example (using ExtJS 7.3.x Material): example
Related
In our ExtJs 5.0.1 project we create components and at runtime we add additional css classes conditional.
We want to find them by a component query but the component query returns nothing for the programatically added css classes.
But when we define them hardcoded in the component config then the query returns the expected result.
How can we get references to the components with programatically added css classes?
Example View:
Ext.define('T.view.Main',{
extend: 'Ext.panel.Panel',
title: 'test',
cls: 'working', // static config
initComponent: function(){
this.callParent(arguments);
this.addCls('notworking'); // added at runtime
}
});
Example Application:
Ext.application({
name : 'T',
autoCreateViewport: 'T.view.Main',
launch: function(){
// the cls "working" assigned as config to the Main View is found by
// the ComponentQuery
var working = Ext.ComponentQuery.query('[cls~=working]');
// the cls "notWorking" assigned by addCls during runtime to the Main View
// is not found by the ComponentQuery
var notWorking = Ext.ComponentQuery.query('[cls~=notworking]');
Ext.Msg.alert('Static vs Dynamic', 'working: ' + working.length + ' notWorking: ' + notWorking.length);
}
});
Update
#Alexander suggested to add the additional cls before the callParent call , which sounds like an obvious solution but now even the .working cls is not found by the component query.
Ext.define('T.view.Main',{
extend: 'Ext.panel.Panel',
title: 'test',
cls: 'working', // static config
initComponent: function(){
this.cls += ' notworking';
this.callParent(arguments);
}
});
See the updated Sencha Fiddle.
Update 2
I may found the problem in the Component.js code, in the constructor the following happens
constructor: function (config) {
...
me.setupProtoEl();
// initComponent, beforeRender, or event handlers may have set the style or `cls` property since the `protoEl` was set up
// so we must apply styles and classes here too.
if (me.cls) {
me.initialCls = me.cls;
me.protoEl.addCls(me.cls);
}
if (me.style) {
me.initialStyle = me.style;
me.protoEl.setStyle(me.style);
}
me.renderData = me.renderData || {};
me.initComponent();
...
the proto el is initialized with some css classes by me.setupProtoEl();
it is checked if cls is set and it is then applied to the proto element and saved to initialCls property
initComponent function is called, when cls is changed in it, it is not noticed by the constructor anymore
In my opinion the steps 2 and 3 needs to be swapped in order to recognize changes of cls in the initComponent function.
cls is a configuration attribute for you to specify the CSS class. But addCls doesn't update it - it simply updates the class attribute on the underlying DOM element.
addCls: function(cls) {
var me = this,
el = me.rendered ? me.el : me.protoEl;
el.addCls.apply(el, arguments);
return me;
},
(source)
Because addCls doesn't update the cls attribute, your ComponentQuery call can't find it that way.
As to how you solve your problem: the simplest way would be to add a property of your own on the class that you update at the same time as you add the class. Then you can do a component query on your custom property. Though I'd include the xtype of your class, so as to avoid potential namespace conflicts.
As #RobertWatkins points out the cls of the component is not changed by addCls, it just updates the DOM element.
A solution to set the cls config during runtime, is to do it in the components constructor. The cls is then applied to the DOM element and to the component aswell. The component query is now able to find it.
cls: 'working',
constructor: function (config) {
this.cls += ' nowworking';
this.callParent(arguments);
},
The corresponding application snippet
// the component is retrieved
var working = Ext.ComponentQuery.query('[cls~=working]');
// the component is retrieved aswell
var nowWorking = Ext.ComponentQuery.query('[cls~=nowworking]');
A working fiddle.
I'm new to extjs and I'm looking for a way to add some custom message under my combobox field.
Depending on some conditions (eg. value selected) the message needs to have different text and/or style.
I could play with errorEl associated with my combobox and change it's message/style depending on the state, but this doesnt look like a good approach.
Are you aware of any plugin allowing to add such a message, or is there a shorter way to do this?
Thank you for your suggestions. I ended up writing my own plugin, which then I attached to combobox.
I added new element after error element and I changed messages based on proper combobox events.
afterCmpRender : function() {
var me = this, cmp = me.getCmp();
var messageWrapper = {
tag : 'div'
};
var messageEl = cmp.errorEl.insertSibling(messageWrapper, "after");
cmp.messageEl = messageEl;
Ext.Array.each(me.messages, function(message) {
var messageConfig = {
tag : 'div',
style : {
display : 'none'
}
};
var newElement = messageEl.insertSibling(messageConfig, "after");
newElement.setHTML(message.value);
newElement.addCls(message.classes);
me.registerMessageEvents(me, cmp, message, newElement);
});
}
I almost always use multiple elements for this, and would not make an attempt to change the field.
Depending on your context, which you didn't provide, I'd say you could have a look at:
Ext.form.field.Display
Ext.form.Label
Ext.tip.Tip
Ext.tip.QuickTip
I'd work with the class Ext.tip.Tip.
You can create
Ext.create('Ext.tip.Tip', {
id: 'myTip',
header: false
});
and then
var tip = Ext.getCmp('myTip');
tip.update('your custom tip message');
tip.showBy(comboboxComponent);
You could also use showAt(..) instead of showBy.
For more information look into the Docu
Here is a Fiddle link to an example.
I'm trying out Protractor to e2e test Angular app and haven't figured out how to detect if an element has a specific class or not.
In my case, the test clicks on submit button and now I want to know if form[name="getoffer"] has class .ngDirty. What may be the solutions?
describe('Contact form', function() {
beforeEach(function(){
browser.get('http://localhost:9000');
element(by.linkText('Contact me')).click();
});
it('should fail form validation, all fields pristine', function() {
element(by.css('.form[name="getoffer"] input[type="submit"]')).click();
expect(element(by.name('getoffer'))).toHaveClass('ngDirty'); // <-- This line
});
});
One gotcha you have to look out for with using toMatch(), as in the accepted answer, is partial matches. For instance, let's say you have an element that might have the classes correct and incorrect, and you want to test that it has the class correct. If you were to use expect(element.getAttribute('class')).toMatch('correct'), that will return true even if the element has the incorrect class.
My suggestion:
If you want to only accept exact matches, you can create a helper method for it:
var hasClass = function (element, cls) {
return element.getAttribute('class').then(function (classes) {
return classes.split(' ').indexOf(cls) !== -1;
});
};
You can use it like this (taking advantage of the fact that expect automatically resolves promises in Protractor):
expect(hasClass(element(by.name('getoffer')), 'ngDirty')).toBe(true);
If you're using Protractor with Jasmine, you could use toMatch to match as a regular expression...
expect(element(by.name('getoffer')).getAttribute('class')).toMatch('ngDirty');
Also, note that toContain will match list items, if you need that.
Simplest is:
expect(element.getAttribute('class')).toContain("active");
Based on the answer from Sergey K, you could also add a custom matcher to do this:
(coffeescript)
beforeEach(()->
this.addMatchers({
toHaveClass: (expected)->
#message = ()->
"Expected #{#actual.locator_.value} to have class '#{expected}'"
#actual.getAttribute('class').then((classes)->
classes.split(' ').indexOf(expected) isnt -1
)
})
)
Then you can use it in tests like this:
expect($('div#ugly')).toHaveClass('beautiful')
And you'll get the following error if it doesn't:
Message:
Expected div#ugly to have class beautiful
Stacktrace:
Error: Expected div#ugly to have class 'beautiful'
Have you tried this?
const el = element(by.name('getoffer'));
expect(el.getAttribute('class')).toBe('ngDirty')
or a variation of the above...
I made this matcher, I had to wrap it in a promise and use 2 returns
this.addMatchers({
toHaveClass: function(a) {
return this.actual.getAttribute('class').then(function(cls){
var patt = new RegExp('(^|\\s)' + a + '(\\s|$)');
return patt.test(cls);
});
}
});
in my test i can now do stuf like this:
var myDivs = element.all(by.css('div.myClass'));
expect(myDivs.count()).toBe(3);
// test for class
expect(myDivs.get(0)).not.toHaveClass('active');
this also works when an element has multiple classes or when an element has no class attribute at all.
function checkHasClass (selector, class_name) {
// custom function returns true/false depending if selector has class name
// split classes for selector into a list
return $(selector).getAttribute('class').then(function(classes){
var classes = classes.split(' ');
if (classes.indexOf(class_name) > -1) return true;
return false;
});
}
This is how I do it at least, without the need to use the expect function. This function simply returns true if the class is inside the element and false if not. This also uses promises so you would use it like:
checkHasClass('#your-element', 'your-class').then(function(class_found){
if (class_found) console.log("Your element has that class");
});
Edit: I just realized this is essentially the same as the top answer
Here a Jasmine 1.3.x custom toHaveClass matcher with negation .not support plus wait up to 5 seconds (or whatever you specify).
Find the full custom matcher to be added on your onPrepare block in this gist
Sample usage:
it('test the class finder custom matcher', function() {
// These guys should pass OK given your user input
// element starts with an ng-invalid class:
expect($('#user_name')).toHaveClass('ng-invalid');
expect($('#user_name')).not.toHaveClass('ZZZ');
expect($('#user_name')).toNotHaveClass('ZZZ');
expect($('#user_name')).not.toNotHaveClass('ng-invalid');
// These guys should each fail:
expect($('#user_name')).toHaveClass('ZZZ');
expect($('#user_name')).not.toHaveClass('ng-invalid');
expect($('#user_name')).toNotHaveClass('ng-invalid');
expect($('#user_name')).not.toNotHaveClass('ZZZ');
});
One way to achieve this would be to use xpath and use contains()
Example:
var expectElementToHaveClass = function (className) {
var path = by.xpath("//div[contains(#class,'"+ className +"')]");
expect(element.all(path).count()).to.eventually.be.eq(1);
};
You can use the CSS parser to handle this by checking if an element with the given class exists:
expect(element(by.css('.form[name="getoffer"].ngDirty')).isPresent()).toBe(true);
Essentially, you're solving a few problems:
how to get class. class is an html attribute and thus can be retrieved by this command (await is the recommended way these days)
let class = await element.getAttribute('class')
Once you got the value of a class, you want to assert it
// for exact value
expect(class).toBe("active");
// for partial match
expect(class).toContain("active");
// or
expect(class.includes("active")).toBe(true);
// BUT, keep in mind
expect('male').toContain('male');
expect('female').toContain('male');
// BOTH pass
I have some data that I'm getting from the server that depending on the situation may bring different fields, so what I have is this:
//This is the way i'm attaching the newly created template to the view
//Still no success
function processDataMethod(response){
//some processing here...
var details = Ext.widget('details');
details.config.itemTpl = new Ext.XTemplate(tplFields);
}
Ext.Ajax.request({
url: '...',
...,
success: function (response, request) {
var combinedData = processDataMethod(response);
operation.setResultSet(Ext.create('Ext.data.ResultSet', {
records: combinedData,
total: combinedData.length
}));
operation.setSuccessful();
operation.setCompleted();
if (typeof callback == "function") {
callback.call(scope || that, operation);
currentList.up().push(Ext.widget('details'));
}
}
});
Any help is appreciated, thanks!!
You have to make a distinction between a number of things:
currentList.up() returns a DOM element (Ext.dom.Element). This has no method push().
With Ext.widget('details', config); you can pass a config like {itemTpl: yourTemplate, data: yourData} to create an instance with a custom template and custom data.
To update your component after creation you can always do someWidget.update(data);.
A component can be rendered to an HTML element with the renderTo option.
A component can be appended to existing components in different ways and you can update the whole layout or parts of it in different ways. This is unnecessary if you are rendering to an HTML element.
Does that help you find your problem?
I have been following:
http://www.sencha.com/learn/Tutorial:Introduction_to_Ext_2.0
And using the following example:
Ext.onReady(function() {
var paragraphClicked = function(e) {
Ext.get(e.target).highlight();
}
Ext.select('p').on('click', paragraphClicked);
});
I am using something very similar:
Ext.onReady(function() {
var paragraphClicked = function(e) {
Ext.get(e.target).addClass('product-selected');
}
Ext.select('.product').on('click', paragraphClicked);
});
However it does not appear to work correctly. e.target appears to return the ext viewport object.
I am actually using Ext 3 not 2 so I guess there must be differences.
I never used e.target, always e.getTarget().
Maybe you can try e.getTarget(".product") ?
Or maybe you can play with the delegate options of addListener in Ext.Element.