ExtJS 4 Change grid store on the fly - extjs

Is it posible to change grid's store in ExtJS 4?
For example, i have two models:
User = Ext.define('User',{
extend: 'Ext.data.Model',
[...],
hasMany: 'Product'
});
Product = Ext.define('Product',{
extend: 'Ext.data.Model',
[...]
});
and two grids.
The first grid is linked with Store which uses User model and loads nested json data from backend, like
{
users: [{
id: 1,
products: [
{id: 1},
{id: 2}
]
}, {
id: 2,
products: [
{id: 3},
{id: 4},
{id: 5}
]
}]
}
All i want to get is when you click on the row in the first grid, the second grid must show products of the user, without connection to the server.
All i know is that user.products(); returns a Ext.data.Store object.
So the idea is to change second grid's store to user.products();, but there is no such method grid.setStore() :-)
Thanks in advance

I think a better solution would be to :
grid1.on('itemclick', function(view, record) {
grid2.reconfigure(record.products());
);

You are looking at stores the wrong way. A store is attached to the grid forever, hence there is no grid.setStore(). You do NOT change a store for a grid, instead you change the DATA in the store for that grid.
Now, solution to your problem: You are correct with the part that you already have a instance of store with your data. ie; user.products(). Still, you will have to create a store for your grid. This store will not have any data though. Now, when you need to display products information, you need to load the grid's store with data. You can use:
load()
loadData()
loadRecord()
to load data into your store. In your case, you can do the following:
myStore = user.products();
grid.getStore().loadRecords(myStore.getRange(0,myStore.getCount()),{addRecords: false});

If you want to attach a store to a grid after the grid has been created, you can use the bindStore() method.
var store = user.products();
grid.getView().bindStore(store);
Alternatively you can also use load(), loadData(), loadRecords() methods, and copy the data into your store.

Abdel Olakara's answer helped me out when I needed to update data on something that didn't have reconfigure (custom class inheriting from Ext.form.FieldSet).
But you don't need to specify addRecords or the range for getRange, because the defaults have us covered in this case.
This means you can do:
myStore = user.products();
grid.getStore().loadRecords(myStore.getRange());

Related

Kendo Grid datasource never updating

Kendo Grid is new to me, so I apologize for the ignorance. I'm writing an angular app that uses a separate service to update a local array. It is store in $scope.searchResults variable. I've initialized the grid using the dataSource ->transport property in the hopes that when the array mentioned above is updated, so too will the datasource and the grid updated accordingly. This is not the case. The array is updated, without any problems, but the datasource is never updated. I'll do my best to paste all the code snippets, and console output below.
Html:
<div class="margin-top-25" ng-show="searchResults">
<div id="report-grid" kendo-grid="grid" options="mainGridOptions"></div>
</div>
DataSource proper of the Grid configuration:
dataSource: {
transport: {
read: function read(options) {
options.success($scope.searchResults);
}
},
schema: {
model: {
id: "id",
fields: {
name: {type: "string"},
dataSource: {type: "string"}
}
}
},
pageSize: 10
}
Function for updating the datasource:
function runSearch() {
RetrieveReportsService.query({name: vm.searchData.name, dataSource: vm.searchData.dataSource},
function success(result) {
$log.log($scope.grid.dataSource);
$log.log($scope.searchResults);
$scope.searchResults = result.elements;
$log.log($scope.searchResults);
$scope.grid.dataSource.read();
$log.log($scope.grid.dataSource);
});
}
Now console output:
First time logging the data source:
O…e.e…d.init {options: Object, _map: Object, _prefetch: Object, _data: ObservableArray.extend.init[2], _pristineData: Array[2]…}
First time logging $scope.searchResults:
[Object, Object]
Second time logging $scope.searchResults:
[Object]
Second time logging the data source:
O…e.e…d.init {options: Object, _map: Object, _prefetch: Object, _data: ObservableArray.extend.init[2], _pristineData: Array[2]…}
Note that each data source has an observable array length of 2, before and after the $scope.searchResults has been updated.
I can drill down into the output if it is needed, but didn't want this post to get overwhelming.
Thanks!
Because you are referencing your data that is declared inside your options object by pointing k–options at your options object when the grid initially binds to the options object it's not populated with the data yet. If you reference your data source object separately using k-data-source it will bind to your data source and update your grid when the data source changes. If you want to make changes to your options object trigger a rerender you need to use k–rebind or alternatively the setOptions method. Be sure to read the docs for the latter as there are some caveats.
Well, I don't know why it is working the way that it is, but with the current project configuration this is the solution.
The grid configuration and the function for updating the data source are in the same module. The grid itself is initialized in another controller. I had move the function for updating to the controller that contained the grid and now it works perfectly.
If anyone knows why, feel free to chime in.

How to generate the right data structure for ngoptions in single request

I'm trying to return a data structure for an Angular screen with several dropdowns.
I don't want to make multiple requests to get the options for each select so I was hoping to create a single nested data structure in Django that contains all the options for each select (there are only a small number)
Angular wants something like this to use with the select and ngoptions directives:
{'booking_name': 'acme',
'current_sales_person': 2,
'sales_people_options': [
{id: 1, name: 'rod'},
{id: 2, name: 'jane'},
{id: 3, name: 'freddy'}
],
... lots more fields here ...
}
sales_people_options would populate the options for the dropdown and current_sales_person indicates which salesperson is initially selected.
How can I get Django Rest Framework to return this in a single request from a ModelSerializer?
I could just create the JSON directly via JSONRenderer but I've got a lot of other fields so I'm keen to use ModelSerializer.
The only way seems to be to add a method called sales_people_options to my Model.
However - I've got several of these fields to generate and it seems very wrong to add model methods for something that I only need for one specific serializer.
EDIT - I think I might need to subclass serializer.Field and override a method (probably field_to_native).
This is just a json object. The Django rest framework is capable of returning json strings which you can then JSON.stringify the string returned to turn it into an object angular is expecting.
{'booking_name': 'acme',
'current_sales_person': 2,
'sales_people_options': [
{id: 1, name: 'rod'},
{id: 2, name: 'jane'},
{id: 3, name: 'freddy'}
Checkout the Django JSON renderer; http://www.django-rest-framework.org/api-guide/renderers
This might be a weird way to do it but it worked:
class BookingSerializer(serializers.ModelSerializer):
class UserOptionsField(serializers.Field):
def field_to_native(self, obj, field_name):
return User.objects.all().values('id', 'username')
sales_people_options = UserOptionsField()
class Meta:
model = Booking
fields = (
'id', 'booking_no', 'current_salesperson', 'sales_people_options',
)
EDIT: And here's a generic solution:
class ModelOptionsField(serializers.Field):
def __init__(self, source=None, label=None, help_text=None, queryset=None, fields=None):
self.queryset = queryset
self.fields = fields
super(ModelOptionsField, self).__init__(source, label, help_text)
def field_to_native(self, obj, field_name):
return self.queryset.values(*self.fields)
that you use like this:
class MySerializer(serializers.ModelSerializer):
my_options_list = ModelOptionsField(queryset=User.objects.all(), fields=('id', 'username'))

Mapping not working when creating a model with Ext.create?

When I create a model using Ext.create() in ExtJs 4.2+, I am expecting the mapping to fill the model but it does not seem to do it. Is it a normal behavior?
If I use a model with mapping in a store, the mapping works fine...
Example not working:
http://jsfiddle.net/B6v6v/
Ext.define('MyApp.model.file', {
extend: 'Ext.data.Model',
fields: [
{
name: 'name',
mapping:'label'
}]
});
var rec = Ext.create("MyApp.model.file",{"label":"TEST"});
console.log(rec.get("name"));
Yes, it's normal. The mapping is for transforming data coming in from the server into something readable in your model. If you already have the data, why not just use the correct key?
If you must, you can do something like:
MyApp.model.File.getProxy().getReader().readRecords([{}, {}, {}]);

Loading multiple extjs comboboxes in a form

another ComboBox question.
In my table there are about 10 fields that are foreign keys, all presented with comboboxes.
How to fill all this combos in a form, without go 10 times to server to load the store of each one?
Are they stored as separate tables on the back end? If yes, then the correct way would be to load them going to the server 10 separate times. You can optimize this scenario by:
loading them all simultaneously
loading them all before you get to that page in advance
But you still would want to have 10 different stores in your ExtJs application.
If you wish to combine them into single store remember couple things
you would need to add additional field into this combined table so you can distinguish different lists.
you would load them all at once
then you would still need to separate them into different store objects because otherwise different UI components (comboboxes) won't be able to show different sets of data
Well known problem :) Typically when I have structure like this
var data = {
ForeignKeyObjectId: 123,
ForeignKeyObject: {
Id: 123,
SomeValue: 'Some text 1'
},
SomeOtherObjectId: 456,
SomeOtherObject: {
Id: 456,
SomeValue: 'Some text 2'
}
//, ... same 8 times more
}
I have to load each combo manually:
var combo1 = this.down('#foreignKeyObjectCombo');
combo1.setValue(data.ForeignKeyObject.Id);
combo1.setRawValue(data.ForeignKeyObject.SomeValue);
combo1.store.loadData([data.ForeignKeyObject], true);
var combo2 = this.down('#someOtherObjectCombo');
combo2.setValue(data.SomeOtherObject.Id);
combo2.setRawValue(data.SomeOtherObject.SomeValue);
combo2.store.loadData([data.SomeOtherObject], true);
// same 8 times more
In one of my previous projects on ExtJs 3 I made some overrides for form and combobox behaviour so that I could use form.getForm().loadData(data) once instead of manually setting value, rawValue like in this example. But that way was implicit, so I like more this way :)
Example:
Model 1
Ext.create('Ext.data.Store', {
model: 'EmployeeType',
data : [
{type: 1, description: 'Administrative'},
{type: 2, description: 'Operative'},
]
});
Model 2
Ext.create('Ext.data.Store', {
model: 'BloodType',
data : [
{type: 1, description: 'A+'},
{type: 2, description: 'B+'},
]
});
Even if your stores have Proxy, you can disable AutoLoad so you can load as many as you want in one single request like this:
Create the stores manually:
employeeType = Ext.create('Ext.data.Store', {model: EmployeeType});
bloodType = Ext.create('Ext.data.Store', {model: BloddType});
Create an Ajax request where you bring all combos at once:
Ext.ajax.request({
url: './catalogs/getalldata',
success: function(response) {
var json = Ext.decode(response.responseText);
employeeType.loadData(json.employeeTypes);
bloodType.loadData(json.bloodTypes);
//...
}
});

Dynamic Model with ExtJS 4

With ExtJS 3.x, I was able to use the "fields" property of a Store, but it seems with ExtJS 4 I have to absolutely use a Model. It's fine, but in my case, it's not a static Model, and I need to define the fields on the fly and sometimes to change them.
I could re-create a Model, but I need to use a different name as it's apparently not possible to modify an exisiting Model, neither delete it. If I try to use Ext.regModel with the same name, ExtJS crashes.
Thanks for your help!
4.1 UPDATE:
As an update... in 4.1 there is now a static method setFields which can be used to define the model prototype fields. It works well in a controller's init method.
When I did this, I wanted to have some static fields defined in the model class and then set some more dynamically. Unfortunately the new setFields method replaces all fields with the argument, it was easy enough to handle though.
This example uses the MVC pattern where my model and store are included in the controller's model array and store array (providing me with the handy getters used below):
Ext.define('ST.controller.Main', {
extend: 'Ext.app.Controller',
models: ['User', 'Reference'],
stores: ['CurrentUser', 'PermissionRef'],
views: ['MainPanel'],
init: function() {
var me = this;
me.getPermissionRefStore().on('load', function(store, records) {
var model = me.getUserModel();
// this returns the static fields already defined
// in my User model class
fields = model.prototype.fields.getRange();
// add the permission options (dynamic fields) to the static fields
Ext.each(records, function(permission) {
fields.push({name: permission.get('name'), type: 'bool'});
});
// 4.1 method to update the User model fields
model.setFields(fields);
// now load the current user (it will use the updated model)
me.getCurrentUserStore().load();
});
}
});
The User model and CurrentUser store are created exactly like regular, non-dynamic models and stores would be and included in their respective controller arrays, the 'User' model is simply missing the dynamic fields which are added as shown above.
I also went into that problem. I have a service which is responsible for fetching metadata from the server and adapting the models and stores to this metadata.
I therefore defined an empty model and configured the store to use this model.
When the meta data is processed, I add the new/additional fields to the prototype of the model like this (metaDataStore is the store containing the meta data, model is the model which can be obtained from the model manager):
var fields = [];
metaDataStore.each(function(item) {
fields.push(Ext.create('Ext.data.Field', {
name: item.get('field')
}));
});
model.prototype.fields.removeAll();
model.prototype.fields.addAll(fields);
When I then call load on a store using this model or create new model instances the new fields are processed correctly.
Here's a very simple example. Just use a normal Ext.data.Store but instead of a model, specify the fields property:
// you can specify a simple string ('totally')
// or an object with an Ext.data.Field ('dynamic')
var fields = ['totally', {name : 'dynamic', type : 'string'}];
var newStore = new MyApp.store.Object({
fields : fields
// other options like proxy, autoLoad...
});
Don't specify a model property - it seems that it would override the fields property.
I also wanted to change the columns and content of an existing grid dynamically:
// reconfigure the grid to use the new store and other columns
var newColumns = [
{header: 'Totally', dataIndex: 'totally'},
{header: 'Dynamic', dataIndex: 'dynamic'}
];
myGrid.reconfigure(newStore, newColumns);
From the Ext JS 4 documentation about the "fields" property of Ext.data.Store:
This may be used in place of
specifying a model configuration. The
fields should be a set of
Ext.data.Field configuration objects.
The store will automatically create a
Ext.data.Model with these fields. In
general this configuration option
should be avoided, it exists for the
purposes of backwards compatibility.
For anything more complicated, such as
specifying a particular id property or
assocations, a Ext.data.Model should
be defined and specified for the model
config.
So be careful - Sencha may remove it in the future.

Resources