I'm new to Ext and I'm struggling to figure out Models Stores and Proxies.
The server returns one large JSON object.
For example.
{
"responseHeader":{
"status":0,
"QTime":12,
"params":{
"facet":"true",
"facet.limit":"40"
}
},
"response":{
"numFound":3806,
"start":0,
"docs":[
{
//Lots of fields
"id":"1234",
...
//Some array properties
"testfield":[
"",
""
],
...
}
]
},
"facet_counts":{
"facet_queries":{
"email":3806
},
"facet_fields":{
"emailaddress":{
},
"subject":{
"candles":136,
"filter":130
},
"fromemail":{
},
//...
},
"facet_dates":{ },
"facet_ranges":{}
},
"highlighting":{
"some doc id":{
"emailtext":[ " Tel.: blah blah <em>blah</em>" ],
"combined":[ "<em>Email</em> To: blah blah blah" ]
}
}
}
I don't want to load this data more than once, I want to grab data from this object, for example the docs object, and put it into a grid. Then pull out another part to put into a selectbox.
How do I load this data once, yet create models and stores to give to grids and selectboxes, from it?
From what I read the proxy holds the servers response? So I tried creating a proxy out side of a store. Thinking I could use the same proxy with more than one store.
var myProxy1 = Ext.create('Ext.data.proxy.Proxy', {
type: 'ajax',
url : '../test',
reader: {
type: 'json',
root: 'responseHeader'
}
});
But when I pass myProxy1 to a store
Ext.define('Test', {
extend: 'Ext.data.Model',
fields: [
{name: 'status', type: 'int'},
{name: 'QTime', type: 'int'},
{name: 'param', type: 'auto'}
]
});
var myStore = Ext.create('Ext.data.Store', {
model: 'Test',
proxy: myProxy1,
autoLoad: true,
listeners:{
load: function( ths, records, successful, operation, eOpts ){
debugger;
}
}
});
It doesn't work. The load event is never fired. No data is loaded. I can see that the proxy made the request, I see the response from the server, but it doesn't load.
If I put the proxy inline it loads.
var myStore = Ext.create('Ext.data.Store', {
model: 'Test',
proxy:{
type: 'ajax',
url : '../test',
reader: {
type: 'json',
root: 'responseHeader'
}
},
autoLoad: true,
listeners:{
load:function( ths, records, successful, operation, eOpts ){
debugger;
}
}
});
I was thinking I could have one proxy, attach it to multiple stores, and just change the reader on it before I load the store.
You are pretty much there, and although I'm pretty sure you are understanding it all, for the benefit of others allow me to give an extended answer and a slightly modified solution to your problem.
Definitions:
The Model - primarily defines the fields a record has.
A Store - holds a collection of records.
A Proxy - facilitates server communication through a chosen method (Ajax, Direct, etc.) and maps CRUD (Create/Read/Update/Destroy) operations when such result from a change in the associated store or model.
A Reader - Tells a proxy how to interpret the data the server returns.
A Grid or Combobox - can display store records.
You scenario is not an uncommon one - while by default ExtJS loads each store separately, it is likely an application would prefer various stores to be loaded at once through a single read call; for example, when rendering one store-dependent component is dependent on another store.
Your code is not far off from achieving this, but here is how I do it. In effect, when a 'master' (Tasks) store loads, the server response also carries the data of a 'slave' (Tags) store, which is then manually loaded to that 'slave' store.
The 'slave' store (notice autoload: false and no read operation):
Ext.define('DL.store.Tags', {
extend: 'Ext.data.Store',
model: 'DL.model.Tag',
// Notice the tags are actually returned when the tasks are loaded and loaded into this store by the TasksStore.
autoLoad: false,
autoSync: true,
proxy: {
type: 'direct',
api: {
create: Tags.Create,
update: Tags.Update,
destroy: Tags.Destroy,
},
reader: {
type: 'json',
root: 'tags'
}
},
});
Then the 'master' store:
Ext.define('DL.store.Tasks', {
extend: 'Ext.data.TreeStore',
model: 'DL.model.Task',
autoLoad: true,
autoSync: true,
root: {
text: 'Root',
id: null,
expanded: true
},
proxy: {
type: 'direct',
api: {
create: Tasks.Create,
read: Tasks.Read,
update: Tasks.Update,
destroy: Tasks.Destroy,
},
},
onProxyLoad: function( aOperation )
{
// A read request for tasks will also return the user tags.
// So feed these tags into their store.
var iResult = aOperation.response.result,
iTagsStore = Ext.StoreManager.lookup( 'Tags' );
if ( Ext.isDefined( iResult.tags ) )
iTagsStore.loadRawData( iResult );
// We need this line for "Tasks" store to load its own data
this.callParent(arguments);
}
});
Basically all it does is it takes part of the server response and loads it manually to the 'slave' store.
The PHP server side code (for tasks read operation) involves:
return array(
'success' => true,
'children' => $iNodes,
'tags' => $iTags
);
Where children is the reader's root of the 'master' store, and tags is additional data that is then loaded into the 'slave' store.
I hope you can work how how to apply these concepts to your code.
Related
I am trying to use Ext's (4.1.1) buffered store/grid combination with data, which I cannot access directly through a rest Api.. or so, but the incoming data is handled by my controller and want just add this data to the buffered grid.
And here comes the problem, when I load 500 items directly to the store, the buffering is working.. Only items which I can see gets rendered, but when I start to store.add(items) then they all gets automatically rendered..
So this is my store & grid:
Store
this.store = Ext.create('Ext.data.ArrayStore', {
storeId: 'reportDataStore',
fields: [
{ name: 'html'}
],
buffered: true,
pageSize: 100,
autoLoad: true
});
Grid
{
xtype: 'gridpanel',
flex: 1,
hideHeaders: true,
store: this.store,
verticalScroller: {
rowHeight: 43
},
disableSelection: true,
columns: [
{ header: '', dataIndex: 'html', flex: 1 }
]
}
Data Controller
...
// somewhere in initialization process of the controller,
// I take the reportDataStore, for later reusing
this.reportDataStore = Ext.getStore('reportDataStore');
...
onNewData: function(data) {
this.reportDataStore.add(data)
}
So my expectation was, that data will get into the store, but only the visible data will get rendered.. Now it is so, that all new data gets rendered.
I wasn't able to produce a working example with the code you give, but I've got something close... How did you even manage to add records to a buffered store backed by a memory proxy?
You should try to push your new data to the proxy directly, and then reload the store, like so:
store.proxy.data.push(data);
grid.view.saveScrollState();
// should probably have been a call to reload(), but then the loading never ends...
store.load({
callback: function() {
grid.view.restoreScrollState();
}
});
See this fiddle that tries to reproduce your setup.
Having a peculiar problem with TreeStore where the rest type treestore adds -1 to name of model which breaks my backend routes.
TreeStore is defined as
Ext.define('Gateway.store.Company', {
extend: 'Ext.data.TreeStore',
model: 'Gateway.model.Company',
proxy: {
type: 'rest',
url: 'http://gateway-email-mock/',
reader: {
type: 'json'
}
}
});
where company model is defined as
Ext.define('Gateway.model.Company', {
extend: 'Ext.data.Model',
fields: [
{name: 'id', type: 'int' },
{name: 'name', type: 'string' }
]
});
The view interested in the store is
Ext.define('Gateway.view.company.Company', {
extend: 'Ext.tree.Panel',
alias: 'widget.company-list',
store: 'Gateway.store.Company',
rootVisible: false
});
When loading the widget the store creates URL
http://gateway-email-mock/Gateway.model.Company-1
How does -1 get there after the model name? I do not suppose this is intentional?
Thank you for any tips.
Rixo is correct, that is the tree store trying to load its root node. The default id generator generates its ID in the following way
idgen: {
isGenerator: true,
type: 'default',
generate: function () {
return null;
},
getRecId: function (rec) {
return rec.modelName + '-' + rec.internalId;
}
}
Which means your root record is rec.modelName + '-' + rec.internalId, which is exactly the request that is being made: http://gateway-email-mock/Gateway.model.Company-1
The solution is to make sure you create a root node with an ID, so that the tree's store will make a request for the correct ID , right it's generating the root node for you and giving it an automatic ID. See http://docs-origin.sencha.com/extjs/4.2.2/source/TreeStore.html#Ext-data-TreeStore-method-load and http://docs-origin.sencha.com/extjs/4.2.2/#!/api/Ext.data.TreeStore-cfg-root
Okay, so I have a view that extends Ext.dataview.component.DataItem' I have this function
onNameButtonTap: function(button, e) {
var record = this.getRecord();
console.log("The tid of this record is: " + record.get('tid'));
}
I am able to get an tid back from this tap, which I would like to use to load a new view that will use this id to alter the proxy url to get back different data. Here is the view:
Ext.define('AIN.view.Headlines.Card', {
extend: 'Ext.NavigationView',
xtype: 'headlineContainer',
config: {
tab: {
title: 'Headlines',
iconCls: 'star',
action: 'headlinesTab'
},
autoDestroy: false,
items: [
{
xtype: 'headlines',
store: 'Headlines',
}
]
}
});
How would I get the url parameter in my store to accept a URL like this one
'http://mywebsite.com/app-feeds/channels/' + tid
Thanks for reading, I am new to this and after hours of google searching I can't quite figure this out.
Update, this is working for me.
var store = Ext.StoreMgr.get('Headlines');
store: store.setProxy({
type: 'jsonp',
url: 'http://awebsite.com/app-feeds/channels/' + tid,
reader: {
type: 'json',
rootProperty: 'nodes',
record: 'node'
}
}).load()
You can set the URL of an Ext.data.Store by doing:
store.getProxy().setUrl('http://mywebsite.com/app-feeds/channels/' + tid);
Note that if you are planning on using this Store in multiple areas that use different URLs, you may want to create a new instance of it when you are setting the URL.
UPDATE -
I HAVE ALSO MADE A MOCK IN JSFIDDLE http://jsfiddle.net/mAgfU/371/
AND WITH A FORM PANEL : http://jsfiddle.net/kA6mD/2/
I have the bellow comboox.
When I use the following code to set the form values:
this.form.loadRecord(data);
the entire form is acting ok except from the combo.
instead of giving me the displayField, I get the valueField in the display.
As you can see in the image above, the combo should show the word "Walla" (displayField) instead of "1" (valueField)
Ext.define('App.view.ForeignCombo', {
extend: 'Ext.form.ComboBox',
alias: 'widget.foreigncombo',
queryMode: 'local',
displayField: 'Name',
valueField: 'Id',
editable: false,
matchFieldWidth: false,
constructor: function(config) {
Ext.apply(this, config);
Ext.define('BrnadComboModel', {
extend: 'Ext.data.Model',
fields: ['Id', 'Name']
});
this.store = Ext.create('Ext.data.Store', {
model: 'BrnadComboModel',
autoLoad: true,
proxy: {
type: 'ajax',
url: '/api/Brand/',
reader: {
type: 'json',
root: 'Results'
}
},
pageSize: 50,
});
this.callParent();
}
}
);
this is how I use it:
{
"xtype": 'foreigncombo'
, "name": 'Brand.Id'
, "fieldLabel": 'Brand.Id'
}
There is no race bewtween the form display and the combo ajax request, the combo store is autoLoad:true, meaning I see that it has already been loaded...
Thanks
I used your fiddle a an example. Place a breakpoint in line 87 (Ext.ComponentQuery.query('comobobox')....), in this fiddle http://jsfiddle.net/kA6mD/9/, and set a watch to Ext.ComponentQuery.query('combobox')[0].store.data.. you'll notice the store has no data. This may be linked to what I mentioned in the comment.
I know there must be a better way of doing this, but what I usually use as a workaround is either load the store at some point before in the app or use a synchronous Ext.Ajax.request and load each record at a time in the store.
As this is a combo for brands I suppose you could load the store before (i.e. app load) and lookup for the store instead of creating a new one each time you create a foreigncombo component, so the first solution should work.
As for the second workaround it should also work, it takes a little bit more coding but its actually pretty easy. It should look something like this...
Ext.Ajax.request({
url:'your/url/',
async:false,
.....
success:function(response){
var records = Ext.JSON.decode(response.responseText);
for(var m=0; m<records.length; m++){
var record = Ext.create('YourBrandModel',{
abbr:records[m].abbr,
name:records[m].name
});
store.add(record);
}
}
})
You should do this as few times as possible as it may slow down the user experience if it gets called everytime you create a "foreigncombo", so checking if this store exists before creating one might be a good idea.
Please take in cosideration that I have not tested this code, so you may have to tweak it a little in order for it to work. But it should get you on tracks.
Ok, so say I have a store server side so we are doing remote everything. Example of stores:
Ext.create('Ext.data.Store', {
model: 'MyApp.model.ContactModel',
remoteFilter: true,
remoteSort: true,
autoLoad: true,
autoSync: true,
storeId: 'ContactStore-1'
});
Ext.create('Ext.data.Store', {
model: 'MyApp.model.ContactModel',
remoteFilter: true,
remoteSort: true,
autoLoad: true,
autoSync: true,
storeId: 'ContactStore-2'
});
I hit a problem when I do the following:
Ext.getStore('ContactStore-1').insert(0,{'name':'say'});
Ext.getStore('ContactStore-2').insert(0,{'name':'hi'});
What happens is that when I look at the DB I end up having 2 entries. I get 'hi' once and 'say' twice. From the looks of it what is happening is that the first insert statement gets sent and then the second insert statement gets sent but with data from both inserts (I assume it's cause they share the same model and thus the same proxy)
Thoughts on how I can resolve this so that it doesn't auto merge insertion requests?
Model for your viewing pleasure:
Ext.define('MyApp.model.ContactModel', {
extend: 'Ext.data.Model',
idProperty: 'idContact',
fields: [
{
name: 'idContact',
type: 'int'
},
{
name: 'name',
type: 'string'
}
],
proxy: {
type: 'direct',
api: {
create: contact.createRecord,
read: contact.getResults,
update: contact.updateRecords,
destroy: contact.destroyRecord
},
reader: {
type: 'json',
root: 'data'
}
}
});
I think you are not returning the correct data from the server side on the create. If you do not return the id the server created on the first insert, ExtJS will still think your "say" item is a phantom. That is, it has not yet been stored server side.
When you do the second insert, the store will do a sync as you have autosync on. Sync will send ALL pending changes. Since your "hi" item is new that will be sent in a POST as is expected. But since your previous "hi" item does not have a server generated id and is still a phantom, it too will be sent in a POST with your second sync (triggered by the insert).
Basically the server must return the new id with the success result set so that ExtJS knows that the item has been stored by the server. Here's an example from my REST API:
Request has this payload (check Chrome Developer tools network tab).
POST to http://localhost:8081/api/channel?_dc=1372594759864
{"id":0,"number":0,"name":"test"}
This is the server response (200 OK):
{
"result": {
"id": 4, // <- important
"number": 3,
"name": "test",
},
"success": true,
"location": "http://localhost:8081/api/item/4",
"userMessage": null,
"userTitle": "Success",
"devErrors": null
}
All fields in the model are updated by the data in the server response, and as such your hi and say items will get their server id's set. When the id is set, the phantom property is set to false. You can have a look in the Ext.data.Model js-file for the source code for this if you want to dig deeper. :)
In your case you must have idContact in the returned object as that is you idProperty.
If you want to suspend auto sync, do your inserts and sync manually and then turn auto syncs back on you can use SuspendAutoSync and ResumeAutoSync.
The best way to add models to stores in my opinion is to create your model, save it, and on success put it in the store. That requires you to have the proxy in each model. That would look something like this:
var hi = Ext.create('MyApp.model.ContactModel', {
name: 'hi'
});
hi.save({
success: function (record, operation) {
Ext.getStore('ContactStore-1').add(hi);
// You could do your second insert here that is dependent on the first to be completed
},
failure: function (record, operation) {
Ext.MessageBox.alert("Error", "Could not save model at this time...");
},
scope: this
});
In this way you could add your second item in the success handler of the first item's save.