The issue is when I change the model of QML editable Combobox the popup list doesn't update. Appending and removing items from model does display correctly, but changing existing item only affects model.
I've found a similar question and it said that this bug had been fixed, but in Qt6 which I'm using I still have that problem.
ComboBox
{
id: comboBoxID
textRole: text
currentIndex: 0
editable: true
model: ListModel
{
id: listModelID
ListElement {text: "test0"}
ListElement {text: "test1"}
}
onActivated:
{
console.log('onActivated() model.get(',currentIndex,').text:', model.get(currentIndex).text);
}
onAccepted:
{
console.log('onAccepted() editText:', editText);
model.get(currentIndex).text = editText;
// model.remove(currentIndex);
// model.insert(currentIndex, {text: editText})
}
}
So here for example when I change first item to "tes" it changes in model, but in popup list first item is still named "test0". Also when I choose "test0" after edit, Combobox displays me correct "tes".
But if I use .append(), .remove() or .insert() methods popup list updates correctly. So I think that .get() or .set() methods simply don't emit some update signal for popup component, but which one?
The ListModel QML type is essentially a very thin implementation of a QAbstractItemModel, but it is super basic. It does not implement any smart logic for emitting dataChanged signals when new data is set.
To be honest, for these reasons, I rarely use the ListModel and nearly always prefer to implement my own custom list model. There's a guide for how to do that.
There is a hack you could use, if you really want to use the out-of-the box QML ListModel, though. You could manually call the signals on the model, forcing the update to happen. For instance, calling the modelReset signal will force every binding to the model to re-evaluate:
import QtQuick 2.15
import QtQuick.Controls 2.15
ComboBox {
editable: true
model: ListModel{
ListElement {text: "test0"}
ListElement {text: "test1"}
}
onAccepted: {
model.set(currentIndex, {text: editText});
model.modelReset();
}
}
Related
Is there a way to select an item from a combobox without having the actual value of the item ?
Lets say we have a combo with Oranges, Apples and Lemons and the actual value of these items are keys that we don't know is there a way to do select by index ?
Or perhaps to retrieve the value based on the displayValue ?
Something like this works:
this.comboBox().setValue(7);
But what if I only have the displayValue of what I want to select? Lets say 'Oranges', how do I select that from the combo ?
One way you could do this would be via execute(). The idea would be to find the correct record in the combobox's store, retrieve the id from that, and then use the id to set the correct value.
In an in-browser scenario, you could just do this directly by inspecting the combobox's store. However, in a WebDriver-based scenario, you don't have access to the context in which the app is running from the spec, so execute() is the easiest way (where there is no other way via the API) to get in there. Here's a simple example:
// Component
Ext.define('Sandbox.view.test.combobox.Base', {
extend: 'Ext.form.field.ComboBox',
id: 'futureCmp',
displayField: 'text',
valueField: 'id',
store: {
fields: ['id', 'text'],
data: [
[1, 'Apples'],
[2, 'Oranges']
]
}
});
// Spec
it('should select correct value from displayField', function () {
var combo = ST.comboBox('#futureCmp');
combo
.execute(function (cmp) {
// we're in the target context now, so we *can*
// interact with Ext JS now
// we could return an object if needed, but we only need the id
return cmp.getStore().findRecord('text', 'Oranges').get('id');
})
.and(function () {
// "executeResult" is the value returned from execute()
// in this case, it's the id of the item we want to select
var id = this.future.data.executeResult;
combo.setValue(id);
// assert that the value has been correctly selected
combo.value(id);
});
});
The nice part about this approach is that it's portable between scenario types; since we're using the API exclusively, we could easily switch between in-browser and web-driver based scenarios and run the same test (in fact, this is borrowed from some internal tests that do precisely that).
Finally, we do have an item in the feature request tracker for adding select() style methods to the combobox future API, similar to what is available for Grid and DataView. I'm not sure when they will make it in, but I've personally been wanting it for a while :)
You can do something like this: ST.ComboBox('${ST_LOCATOR}').setValue('STRING_VALUE')
Where STRING_VALUE is one of the options available in the combobox.
Please note that STRING_VALUE is not the value you see in UI. you can have in UI 3D Pie and in component 3d_pie. The second is the one you'll have to use.
If the component is not quite a real ComboBox but a more complicated component you'll have to find workarounds with DOM elements and click actions, etc.
Assuming we have 1 parent viewmodel (corresponding to viewport) and 2 nested (grid and window) viewmodels. On grid actioncolumn click we get a record id, pass it as an ajax request url part and show the response into the window textfields. If no values presented in the response show defaults.
The interesting part is, how to pass the response data to the window textfields using viewmodel binding. I came up with the following solution:
1) On ajax request create a grid viewmodel links using the response data which will be passed through the model, creating defaults if needed:
grid.getViewModel().linkTo('custom', {
reference: 'CustomModel',
create: data
});
Ext.define('CustomModel', {
extend: 'Ext.data.Model',
fields: [
{ name: 'name', defaultValue: 'default name' },
{ name: 'value', defaultValue: 'default value' }
]
});
2) Using formulas in the grid viewmodel publish data to the parent viewmodel:
formulas: {
publishCustom: function(get) {
this.set('main.custom', get('custom'));
}
}
3) Populate window textfields with a data from the parent (viewport) viewmodel
bind: '{main.custom.name}'
You may find the fiddle here.
This seems a bit not obvious and badly creates an extra data copy. Is there a more cleaner solution for such an issue?
Much cleaner approach would be to store the commonly accessed objects only in the top level main ViewModel, as all child VM's inherit the data objects in the parent VM's.
You can still bind to them or use in formulas as if they were in the child VM.
See:
ViewModel Data and Inheritance
I am using Backbone.Notifier for showing alerts. How could I display custom backbone view inside it?
Any suggestion?
Don't think it's suited to adding your own custom view. Customization of the notifications view comes through CSS.
For customising buttons you can use the css property :
buttons: [
{'data-role': 'myOk', text: 'Sure', 'class': 'default', css: {width: 120}},
{'data-role': 'myOk', text: 'Yes'}]
For customising the base notification window use the 'notifier' CSS class.
You can change this with the 'baseCls' property on the notifier.
Unfortunately I don't think there's a way of assigning a Backbone view to the notifier but if it's just customization of the aesthetics you want then hopefully the CSS is enough.
If you really wanted to go for a hacky approach you could use the NotificationView which is a standard Backbone View (part of the Notifier class - Backbone.Notifier.NotificationView). You could try overriding this to your implementation but it's definitely a hack so wouldn't recommend it. It's worth taking a look at the notifer.js source code.
To show my custom view inside backbone.notifier i am adding following lines inside the plugin
In notify function before the return statement
.......
if(options.custView){
msgInner.off('click'); //the turn off default behaviour which is to destroy view on click
options.custView.destroyNotifier = removeFn; //now in the custom view i just call this.destroyNotification to destroy the notification
msgView.$el.find('.notifier-message').html(options.custView.render().el); //pasting my view on notification to display
}
return msgView;
}
This is how I now call the plugin
var notifier = new Backbone.Notifier({
el : 'body',
theme : 'clean'
});
notifier.notify({
custView : (new SomeView({
x : 'xyz'
})),
ms : false, //to aviod a timeout
destroy : true
})
I have little experience with ExtJS3 and now starting with version 4.
In my controller, I have this:
init: function ()
{
this.control({
"userlist":
{
selectionchange: function (view, selected, opts)
{
//get to grid??
}
}
});
}
How can I access the grid that this event happened on, without using id's?
I want to enable/disable buttons on the grid toolbar (tbar) if there are items selected, but I don't want to give anything id's (not the bar, not the individual buttons)
EDIT: the solution was to use the refs property in the controller:
refs:
[
{
ref: "list",
selector: "userlist"
}
],
selectionchange: this.activateTbButtons
activateTbButtons: function (selected, opts)
{
if (selected.selected.length == 1)
{
var tb = this.getList().query("toolbar");
}
}
Just found out that you can use the attribute view, and views under Ext.selection.Model.
This can be useful in cases when you let's say open multiple instances of your objects.
So, to access the grid in your example:
selectionchange: function (view, selected, opts) {
//get to grid??
var grid = view.view.ownerCt;
}
Having the same problem and found the previous answers missing some points. In short, I recommend:
selectionchange: function (selModel, selected, eOpts) {
var grid = selModel.view.ownerCt;
}
This was already proposed by Adezj although it referred to the selectionchange event that has the view as the first argument, and is not applicable to ExtJS 4.0.7+. (Don't think that selectionchange ever had the view as an argument?)
Note that this might not be officially supported by ExtJS since the view property of the selection model is not mentioned in the API docs at all.
Another approach is to use Ext.ComponentQuery.query(...) or defining refs in the controller, as proposed by Arun V, which is basically just a handy wrapper for Ext.ComponentQuery.query(). This works fine if you only have individual instances of the grid class but you need to take care in case you have multiple instances of the same grid class. Simply doing Ext.ComponentQuery.query('xtype-of-your-grid') will return all instances of your grid and you will have lots of fun finding out in which one the user has selected something.
So, in general, I would highly recommend to always work your way up from the component or object that fired the event to be sure you are in the right branch of the component hierarchy unless you are sure you will never have more than one instance of that class you write a controller for.
EDIT
I took a look at the docs for the selectionChange event:
selectionchange( Ext.selection.Model this, Ext.data.Model[] selected, Object eOpts )
The view is not being passed in to the selectionchange handler. An easy way to handle this is to either use Ext.getCmp() or use refs as seen in the docs for Ext.app.Controller:
http://docs.sencha.com/ext-js/4-0/#!/api/Ext.app.Controller
//get grid
var grid = selectionModel.view.ownerCt.ownerCt;
I have a button in a Sencha Touch 2 project.
The button gets destroyed with the view after being pressed and is rebuild after another button gets pressed.
But the button does not get the listener again.
the listener is build in the controller of the view.
Ext.application({
name: 'App',
controllers: ['Main','Home'],
views: ['Main','Home'],
launch: function () {Ext.Viewport.add({xtype:'mainview'});}
});
the controller
Ext.define('App.controller.Home', {extend: 'Ext.app.Controller',
config: {
refs: {homeView: '#homeview',backBtn: '#btn_test1'},
control: {
backBtn: {
tap: function(backBtn){
console.log('[Controller][Home] btn monatsrate - - tap');
Ext.Viewport.add({xtype: 'mainview'});
Ext.Viewport.setActiveItem(1);
}
},
homeView: {
deactivate: function (homeView){
console.log('[Controller][Home] autodestroy homeview');
//homeView.destroy();
Ext.Viewport.remove(homeView);
}
}
}
},
});
And the view
Ext.define("App.view.Main", {
extend:"Ext.Container",
xtype:"mainview",
config:{
id:'mainview',
items:[
{
xtype:'button',
id:'btn_test2',
text: 'test2'
}
]
},
});
Any idea how to allow the button to get the listener back?
This is because the "ref" in your controller is using the id of the button to create the ref. Instead, use a different selector for your button. For example you could give your button a "name" property and give it a value of "testbutton". Then your ref would be like
refs: {homeView: '#homeview',backBtn: 'button[name=testbutton]'},
I struggled with this same problem for buttons and list items that were created/destroyed many times throughout the application's flow. Since then I've read a few times that, in general, the Sencha Touch team recommends not using the id as the selector unless you have a specific reason to. The "name" method above works very well for me. You could use lots of other css-style selectors as well (you'd have to read up on that separately).
As mentioned in a previous comment, I would accept some answers to increase the probability of getting an answer to your questions in the future. I'm just answering this one because I beat my head against the wall on this issue for 4 hours.
Sencha's examples recommend using action config on buttons, like 'cancel', 'goHome', 'createPost', etc.. which kinda makes sense.
All refs are then in the form of: myContainer button[action=myAction]
I believe your issue is exactly the id parameter. If you ever add any id you should make sure it is unique, thus adding an id to a config of your custom view will result in no way to create more then one instance of it! I may not be a 100% right(might be inside a container but i believe it will cause issues anyway) but why would you want an id that much? Besides, you can simply reference your view by xtype:
refs: {homeView: 'homeview',backBtn: 'btn_test1'},
regards,