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
Related
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();
}
}
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.
I coded a simple application which displays a dataview.List articles in the home page.
Here's a view of my home page :
I can also open a specific article by clicking on it and have its complete description in an other view.
Here's my article view page for a specific article :
To do this I apply a filter in my ArticleController class (here I get the matched record).
onViewArticle: function(record) {
var articleStore = Ext.getStore("ArticleStore");
var selectedArticle = record;
articleStore.filterBy(function(record, id) {
if (selectedArticle.data.id == id)
return (true);
return (false);
});
Ext.Viewport.animateActiveItem(this.getArticleViewConnexContainer(), {
type: "slide", direction: "left"
});
},
And here's my store class :
Ext.define("MyApp.store.ArticleStore", {
extend: "Ext.data.Store",
requires: ["MyApp.model.ArticleModel"],
config: {
model: "MyApp.model.ArticleModel",
proxy: {
type: "ajax",
api: {
create: "http://localhost/MobileApplication/MyApp/services/ArticleService.php?action=create",
read: "http://localhost/MobileApplication/MyApp/services/ArticleService.php?action=read",
update: "http://localhost/MobileApplication/MyApp/services/ArticleService.php?action=update",
destroy: "http://localhost/MobileApplication/MyApp/services/ArticleService.php?action=destroy"
},
extraParams: {
keyword: ""
},
reader: {
type: "json",
rootProperty: "articles",
totalProperty: "total"
}
},
autoLoad: true
}
});
But now, I want to apply another filter in the same action to list others articles (with another specific filter) just below the article description. This involve to use two differents filters I think (one to get back the specific article (already done here) and an other one for the article list I want just below the article description). But how can I do this ? If I apply two filters in the same controller function, the second one will destroy the prevent one because all store data are in the cache. Is there a possible way (like in php MVC frameworks for instance) to send a variable from the controller to the view and display its content (By this way I will have two differents variables and I will can display the content of my two requests on my view)? Or maybe a possible way to handle several stores in the same time ? I'm really lost. Does anyone can help me, please ? Thanks in advance for your help.
There are actually couple questions inside.
Sencha Framework is built with "one store - one view" concept in mind. So if you have two different view presenting information from really one data store, you still would need to have two copies of this store. You can clear/apply back filters if you don't need to show these two views at the same time (it's mostly true in case of phone applications), but I would recommend to have two separate copies.
As far as your scenario - I don't think you need that. When you're displaying particular book you don't need to filter store. You need to just load one record (store.getAt()) and then use this record in your child form.
I want bind the data to label control at controller level. i have a main Tab-Panel view within view have two more view like example1 view and specification view. in specification tab view have a label is id: lblSpecification, for this label i am going to bind data at controller level as shown below. But it is not working.
controller code is here:
config: {
refs: {
specificationPage: "specification",
specificationLabel: "#lblSpecification"
},
control: {
specificationPage: {
initialize: "SpecificationInitialize"
}
},
SpecificatiTabInitialize: function () {
this.getSpecificationLabel().setHtml("Welcome");
}
}
I have created another similar project, where I am not using tab panel, I have followed similar steps as code mentioned above, its working fine, please can I know its the problem due to tabpanel or is their any alternate way to achieve this?
Try to setHtml when label is initialized but not a panel.
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;