I'm trying to display NestedList with json on my hard disk, but it is not displayed. What am I doing wrong? No bugs in Chrome. (Chrome open with Applications / Google \ Chrome.app / Contents / MacOS / Google \ Chrome - allow-file-access-from-files). I get a blank screen without a single error in the console. If I comment fullscreen: true, in MyPanel.js I get clear NestedList without any data.
Servlet.json
{
"stream":[{
"post_id": "1",
"post_type": "text",
"post_thumb": "bla1"
}]
}
MyPanel.js
Ext.define('MyApp.view.MyPanel', {
extend: 'Ext.dataview.NestedList',
alias : 'widget.MyPanel',
config: {
store : 'Storere',
title: 'NestedList Example',
detailCard: {
html: 'You can see detail information here!'
}
}
});
Storere.js
Ext.define('MyApp.store.Storere', {
extend: 'Ext.data.TreeStore',
config: {
model: 'MyApp.model.MyModel',
defaultRootProperty : 'stream',
proxy: {
type: 'ajax',
url: '.\/app\/Servlet.json',
reader: {
type: 'json',
rootProperty: 'stream'
}
}
}
});
MyModel.js
Ext.define('MyApp.model.MyModel', {
extend: 'Ext.data.Model',
config: {
fields: [
{name:'post_id', type: 'string' },
{name:'post_type', type: 'string' },
{name:'post_thumb', type: 'string' }
]
}
});
TheWorld.js
Ext.define('MyApp.controller.TheWorld', {
extend : 'Ext.app.Controller',
config: {
profile: Ext.os.deviceType.toLowerCase(),
control: {
'MyPanel': {
activate: 'onActivate',
leafitemtap: 'onDetailDisplay'
}
}
},
onActivate: function() {
console.log('Main container is active');
},
onDetailDisplay: function(nestedList, list, index, target, record) {
console.log('onDetailDisplay is active');
var detailCard = nestedList.getDetailCard();
},
onItemTap: function(view, list, index, target, record, event) {
console.log('Item was tapped on the Data View');
},
init: function() {
console.log('Controller initialized');
},
});
UPD:
You need to change NestedList config to following.
config: {
store : 'Storere',
//displayField:'post_type',
title: 'NestedList Example',
listConfig : {
itemTpl: '{post_type}'
},
detailCard: {
html: 'You can see detail information here!'
}
}
There is this displayField config of NestedList where you can specify which field to be used to set title and item text . By default it is set to text but you can specify it to be one of your model field. If you are overriding getItemTextTpl or getTitleTextTpl, this config will be ignored.
Plus there's listConfig config option available where you can mention itemTpl config. As json is having multiple fields with image and text nodes I assume you want to show that too.
So you can have listConfig something like following :
listConfig: {
itemTpl: '<div><img src="{post_thumb}">{post_type}</div>'
},
Reasons of list was not getting displayed might be-
Store is not loaded with proper data. ( To make sure inspect Network tab in chrome to see if Servlet.json present.
displayField and/or listConfig not mentioned.
Related
To reproduce this issue, I created a fiddle: https://fiddle.sencha.com/#view/editor&fiddle/3mk0 . The steps to reproduce are as follows:
Open your favorite browser and open the debugging tools to be able to view the JavaScript console
Navigate to the fiddle and run it
Double-click on a row - the grid is empty. Check the JS console: Uncaught TypeError: store is null (I used FF). The crux of the issue is that, in this line, let store = vm.getStore('testStore');, vm.getStore('testStore') returns null the first time. That's what I am trying to understand - why doesn't ExtJs initialize completely the ViewModel and the store, and instead it returns null. The problem is the binding in the url of the proxy. If the store doesn't have any binding in the url, it's going to work the first time as well.
Close the window, and double-click again on a row. This time the grid will show the data.
I know how to fix this - if I issue a vm.notify() before I set the store, it's going to work properly (I added a commented out line in the code).
Here is the source code from the fiddle:
app.js
/// Models
Ext.define('App.model.GroupRecord', {
extend: 'Ext.data.Model',
alias: 'model.grouprecord',
requires: [
'Ext.data.field.Integer',
'Ext.data.field.String'
],
fields: [{
type: 'int',
name: 'id'
}, {
type: 'string',
name: 'description'
}]
});
Ext.define('App.model.SomeRecord', {
extend: 'Ext.data.Model',
requires: [
'Ext.data.field.Integer',
'Ext.data.field.String'
],
fields: [{
type: 'int',
name: 'id'
}, {
type: 'string',
name: 'description'
}, {
type: 'int',
name: 'groupId'
}]
});
//// SomeGridPanel
Ext.define('App.view.SomeGridPanel', {
extend: 'Ext.grid.Panel',
alias: 'widget.somegridpanel',
requires: [
'App.view.SomeGridPanelViewModel',
'App.view.SomeGridPanelViewController',
'Ext.view.Table',
'Ext.grid.column.Number'
],
controller: 'somegridpanel',
viewModel: {
type: 'somegridpanel'
},
bind: {
store: '{testStore}'
},
columns: [{
xtype: 'numbercolumn',
dataIndex: 'id',
text: 'ID',
format: '0'
}, {
xtype: 'gridcolumn',
dataIndex: 'description',
text: 'Description'
}]
});
Ext.define('App.view.SomeGridPanelViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.somegridpanel',
requires: [
'Ext.data.Store',
'Ext.data.proxy.Ajax',
'Ext.data.reader.Json'
],
data: {
groupId: null
},
stores: {
testStore: {
model: 'App.model.SomeRecord',
proxy: {
type: 'ajax',
url: 'data1.json?groupId={groupId}',
reader: {
type: 'json'
}
}
}
}
});
Ext.define('App.view.SomeGridPanelViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.somegridpanel',
onRefresh: function (groupId) {
console.log('calling the grid panel onrefresh for groupId: ' + groupId);
let vm = this.getViewModel();
console.log(vm);
vm.set('groupId', groupId);
//vm.notify(); // <- uncomment this line to make it work properly
let store = vm.getStore('testStore');
//tore.proxy.extraParams.groupId = groupId;
store.load();
}
});
// TestWindow
Ext.define('App.view.TestWindow', {
extend: 'Ext.window.Window',
alias: 'widget.testwindow',
requires: [
'App.view.TestWindowViewModel',
'App.view.TestWindowViewController',
'App.view.SomeGridPanel',
'Ext.grid.Panel'
],
controller: 'testwindow',
viewModel: {
type: 'testwindow'
},
height: 323,
width: 572,
layout: 'fit',
closeAction: 'hide',
bind: {
title: '{title}'
},
items: [{
xtype: 'somegridpanel',
reference: 'someGrid'
}]
})
Ext.define('App.view.TestWindowViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.testwindow',
data: {
title: 'Test Window'
}
});
Ext.define('App.view.TestWindowViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.testwindow',
listen: {
controller: {
'*': {
loadData: 'onLoadData'
}
}
},
onLoadData: function (groupRecord) {
console.log('Loading data...');
console.log(groupRecord);
let vm = this.getViewModel();
vm.set('title', 'Group: ' + groupRecord.get('description'));
this.lookup('someGrid').getController().onRefresh(groupRecord.get('id'));
}
});
// ViewPort
Ext.define('App.view.MainViewport', {
extend: 'Ext.container.Viewport',
alias: 'widget.mainviewport',
requires: [
'App.view.MainViewportViewModel',
'App.view.MainViewportViewController',
'Ext.grid.Panel',
'Ext.view.Table',
'Ext.grid.column.Number'
],
controller: 'mainviewport',
viewModel: {
type: 'mainviewport'
},
height: 250,
width: 400,
items: [{
xtype: 'gridpanel',
title: 'Main Grid - double-click on a row',
bind: {
store: '{groupRecords}'
},
columns: [{
xtype: 'numbercolumn',
dataIndex: 'id',
text: 'Group id',
format: '00'
}, {
xtype: 'gridcolumn',
flex: 1,
dataIndex: 'description',
text: 'Group'
}],
listeners: {
rowdblclick: 'onGridpanelRowDblClick'
}
}]
});
Ext.define('App.view.MainViewportViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.mainviewport',
requires: [
'Ext.data.Store',
'Ext.data.proxy.Memory'
],
stores: {
groupRecords: {
model: 'App.model.GroupRecord',
data: [{
id: 1,
description: 'Group 1'
}, {
id: 2,
description: 'Group 2'
}, {
id: 3,
description: 'Group 3'
}, {
id: 4,
description: 'Group 4'
}, {
id: 5,
description: 'Group 5'
}],
proxy: {
type: 'memory'
}
}
}
});
Ext.define('App.view.MainViewportViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.mainviewport',
onGridpanelRowDblClick: function (tableview, record, element, rowIndex, e, eOpts) {
if (!this._testWindow) {
this._testWindow = Ext.create('widget.testwindow');
}
this._testWindow.show();
this.fireEvent('loadData', record);
}
});
Ext.application({
//name : 'Fiddle',
models: [
'SomeRecord',
'GroupRecord'
],
views: [
'MainViewport',
'TestWindow',
'SomeGridPanel'
],
name: 'App',
launch: function () {
Ext.create('App.view.MainViewport');
}
});
}
data1.json:
function(params, req, Fiddle) {
var range = [];
for (var i = 1; i <= 50; i++) {
var obj = {
id: i,
description: `Item ${i}`,
groupId: Math.floor( (i - 1) / 10) + 1
}
range.push(obj);
}
let allData = range.filter(it => it.groupId === params.groupId )
//console.log(params);
//console.log(req);
return allData;
}
The code is a bit contrived but it follows an issue (though not identical) I had in the real app. In the real app I have a weird intermittent issue, where I have a complex form with subcomponents, and a vm.notify() call made in a subcomponent, fires a method bound to a ViewModel data member (in other words it fires when that data member changes), which in turn tries to refresh a local store in another subcomponent but this subcomponent's ViewModel getStore() call returns null. The proxy of that store has the url property bound to a ViewModel data member. That's the common pattern. It seems to me that stores with proxies that have the url property bound to a ViewModel data property (ex: url: 'data1.json?groupId={groupId}') are not initialized properly by the time the form is rendered, and eventually, after some ViewModel computation cycles, they get initialized finally.
TIA
Update: There was a question below whether the url attribute of the proxy is bindable. I think it is bindable. Sencha Architect shows it as bindable, though, when enabled, it doesn't place the property in a bind object.
I did search the ExtJs 7.6.0 code base for samples where the {...} expressions are used in the url attribute, and I stumbled upon this test case:
packages\core\test\specs\app\ViewModel.js from line 7237:
describe("initial", function() {
it("should not create the store until a required binding is present", function() {
viewModel.setStores({
users: {
model: 'spec.User',
proxy: {
type: 'ajax',
url: '{theUrl}'
}
}
});
notify();
expect(viewModel.getStore('users')).toBeNull();
setNotify('theUrl', '/foo');
var store = viewModel.getStore('users');
expect(store.isStore).toBe(true);
expect(store.getProxy().getUrl()).toBe('/foo');
});
it("should wait for all required bindings", function() {
viewModel.setStores({
users: {
model: 'spec.User',
proxy: {
type: 'ajax',
url: '{theUrl}',
extraParams: {
id: '{theId}'
}
}
}
});
notify();
expect(viewModel.getStore('users')).toBeNull();
setNotify('theUrl', '/foo');
expect(viewModel.getStore('users')).toBeNull();
setNotify('theId', 12);
var store = viewModel.getStore('users');
expect(store.isStore).toBe(true);
expect(store.getProxy().getUrl()).toBe('/foo');
expect(store.getProxy().getExtraParams().id).toBe(12);
});
});
What is interesting is that it is expected in the test that viewmodel getStore would return null before setting the value for theUrl: expect(viewModel.getStore('users')).toBeNull(); !
Maybe it is by design...
Reason for the issue is - triggered event listener is getting executed before the View Model is initialised and grid is rendered
We can use following approach to fix these issues
Add delay before triggering the event
Listen for initViewModel method in controller and then load the store
Or add afterrender event on the grid, read its store and then load it
// Add delay to ensure view model is initialized
Ext.defer(function() {
this.fireEvent('loadData', record);
}, 1000, this);
There is a override function called "init" in each controller so use your initial code in this function. This function invokes every time when UI refresh/added new.
Example:
init: function() {
var vm = this.getViewModel();
var store = vm.data.myCurrentStore;
store.load();
}
I have store which return data from server:
Ext.define('Admin.store.dashboard.NPS', {
extend: 'Ext.data.Store',
alias: 'store.nps',
autoLoad : false,
proxy : {
type: 'api',
url : SITE_URL + '/api/surveys/nps'
},
fields : [
{
type: 'float',
name: 'nps'
}
]
});
And I want to display that data in tpl of Ext.Component:
Ext.define('Admin.view.dashboard.NPSPercent', {
extend: 'Ext.Component',
xtype: 'nps-percent',
bind: {
data: {
nps_percent: '{nps}'
}
},
tpl: new Ext.XTemplate('<div class="percent">+{nps_percent.nps}</div>')
});
I tried bind data to loaded store but it dose not work.
Templates in components expect regular things like arrays, not stores. To render a template using a store as the datasource, use Ext.view.View class instead of just a component.
How can I call a new view on image tap which is defined in tpl.
Code:
tpl: Ext.create('Ext.XTemplate','<tpl for="sample">',
'<div> {tittle}</div>
<div><img src="{photo}"/></div>',
'</tpl>',
/////////////////////////////////////////
After your directions, I'm sure I have something wrong. I just want to know how to link an image to puncture it take you to another view. Let me explain better?
Thank you very much for your time and dedication.
//MyController
Ext.define('DemoApp.controller.ControllerR', {
extend: 'Ext.app.Controller',
config: {
refs: {
res: 'res',
},
control: {
'rest list': {
itemtap: 'showR' },
}
},
onCreateNewView: function() {
if(document.getElementById('something')) {
Ext.Viewport.setActiveItem(Ext.create('DemoApp.view.DetalTwo'));
}
},
});
//My Template
Ext.define('DemoApp.view.ViewR', {
extend: 'Ext.Panel',
xtype: 'res',
config: {
title: 'Res',
iconCls: 'info',
scrollable: true,
data: {
res: [ model: 'DemoApp.model.ModelR',
autoLoad: true,
storeId: 'resStore',
proxy: {
type: 'ajax',
url: 'data/res.json',
reader: {
type: 'json',
rootProperty:'wha.res'
}
}]
},
tpl: Ext.create('Ext.XTemplate','<tpl for="sample">',
'<div> {tittle}</div>
<div><img id="something "src="{photo}"/></div>',
'</tpl>',
{
join: function(value) {
return value.join(', ');
}
})
}
});
You can give an id to image tag i.e. <img id = "something"/> and if tpl is defined for list then go to that list's handling function in controller or in view(where you defined a list and listeners) and write this code:
if(document.getElementById('Your img tag id')) {
// create a view and active it
}
For Example:
onCreateNewView: function() {
if(document.getElementById('something')) {
Ext.Viewport.setActiveItem(Ext.create('DemoApp.view.NewView'));
}
}
First thing first... you should do some google before putting such question in Stack.Your Code is totally wrong as well as it contains major syntax errors. And we are not here to solve some stupid syntax errors.Anyways Let me explain something..
1.) In your controller you are trying to bind 'itemtap' event.You can only bind 'itemtap' event if there is a list in your view. Here, in your case there is no list.
2.) And in controller it's look like something this:
refs: {
imageList: '#imageList',
},
control: {
'imageList': {
itemtap: 'onCreateNewView'
},
}
3.) Create a list in your view:
config: {
items: [
{
xtype: 'list',
id: 'imageList',
itemId: 'imageList',
tpl:'Your tpl code'
}
]
}
Ext.Loader.setConfig({enabled: true});
Ext.Loader.setPath('Ext.ux', '/ux');
Ext.require([
'Ext.tip.QuickTipManager',
'Ext.menu.*',
'Ext.form.field.ComboBox',
'Ext.layout.container.Table',
'Ext.container.ButtonGroup',
'Ext.form.FieldSet',
'Ext.data.*',
'Ext.form.*'
]);
Ext.onReady(function() {
Ext.QuickTips.init(); //tips box
Ext.define('MyApp.view.MyContainer13', {
extend: 'Ext.panel.Panel',
alias: 'widget.myroutegridpanel',
width: window.innerWidth,
header: false,
height: window.innerHeight,
//renderTo: Ext.getBody(), //cannot put in everywhere, if not this panel will full screen, can't see other already
layout: {
align: 'stretch',
type: 'hbox'
},
items: [
{
xtype: 'container',
flex: 1,
//maxWidth: window.innerWidth/3,
items: [
{
//xtype: 'mygridpanelroutename'
xtype: 'myroutegridpanel1'
}
]
}
]
});
// THIS IS THE PART I ADD IN, THEN ERROR OCCUR, until below ,please check my remark
Ext.create('Ext.window.Window', {
alias: 'widget.myroutegridpanel1',
width: 400,
height: 400,
title: 'Example of Dynamic Grid',
layout: 'fit',
items: [
{
// All what you have to set! :)
xtype: 'dynamicGrid',
url: 'route/get-routefare.php'
}
]
});
Ext.define('Ext.ux.grid.DynamicGrid', {
extend: 'Ext.grid.Panel',
alias: 'widget.dynamicGrid',
alternateClassName: 'Ext.grid.DynamicGrid',
// URL used for request to the server. Required
url: '',
initComponent: function() {
var me = this;
if (me.url == '') {
Ext.Error.raise('url parameter is empty! You have to set proper url to get data form server.');
}
else {
Ext.applyIf(me, {
columns: [],
forceFit: true,
store: Ext.create('Ext.data.Store', {
// Fields have to be set as empty array. Without this Ext will not create dynamic model.
fields: [],
// After loading data grid have to reconfigure columns with dynamic created columns
// in Ext.ux.data.reader.DynamicReader
listeners: {
'metachange': function(store, meta) {
me.reconfigure(store, meta.columns);
}
},
autoLoad: true,
remoteSort: false,
remoteFilter: false,
remoteGroup: false,
proxy: {
reader: 'dynamicReader',
type: 'rest',
url: me.url
}
})
});
}
me.callParent(arguments);
}
});
Ext.apply(Ext.data.Types, {
FLOATORSTRING: {
convert: function(v, n) {
v = Ext.isNumeric(v) ? Number(v) : v;
return v;
},
sortType: function(v) {
v = Ext.isNumeric(v) ? Number(v) : v;
return v;
},
type: 'floatOrString'
}
});
Ext.define('Ext.ux.data.reader.DynamicReader', {
extend: 'Ext.data.reader.Json',
alias: 'reader.dynamicReader',
alternateClassName: 'Ext.data.reader.DynamicReader',
readRecords: function(data) {
if (data.length > 0) {
var item = data[0];
var fields = new Array();
var columns = new Array();
var p;
for (p in item) {
if (p && p != undefined) {
// floatOrString type is only an option
// You can make your own data type for more complex situations
// or set it just to 'string'
fields.push({name: p, type: 'floatOrString'});
columns.push({text: p, dataIndex: p});
}
}
data.metaData = { fields: fields, columns: columns };
}
return this.callParent([data]);
}
});
// UNTIL HERE, THOSE CODE AFTER ADDED, then returned ERROR in the picture below
}); // on ready
You can see the code i have comment where i have added the code then the error occur, however, i would like to learn how to traceback the error come from, unfortunately, ext-all-debug.js only display those simple error comment, how to trace where is the error come from?
There are a couple things you can do
reference the ext-dev.js instead. The difference has to to do with the differences between the two files. The ext-debug.js file contains all the supporting javascript uploaded at once. The ext-dev.js file contains only the core files and need to load the supporting ones as needed. We get much more informative errors this way. In this case the alias is not recognized.
Second, use Chrome tools and right click and choose Inspect Element. You can see the actual line where the code fails in the sources tab by clicking twice on the pause button on the upper right corner of the developer tools area. It will turn purple. Screen below shows how we now see the line of code that fails highlighted
So I have a parent and child store, illustrated here:
Parent Model
Ext.define('APP.model.Client', {
extend: 'Ext.data.Model',
requires: [
'APP.model.Website', 'Ext.data.association.HasMany', 'Ext.data.association.BelongsTo'],
fields: [{
name: 'id',
type: 'string'
}, {
name: 'name',
type: 'string'
}, {
name: 'slug',
type: 'string'
}, {
name: 'active',
type: 'boolean'
}, {
name: 'current',
type: 'boolean'
}],
hasMany: {
model: 'APP.model.Website',
name: 'websites'
}
});
Child Model
Ext.define('APP.model.Website', {
extend: 'Ext.data.Model',
fields: [{
name: 'id',
type: 'string'
}, {
name: 'client_id',
type: 'string'
}, {
name: 'sub_domain',
type: 'string'
}, {
name: 'active',
type: 'boolean'
}],
belongsTo: 'APP.model.Client'
});
Using an AJAX call via the server, I am loading the Clients store, and that is loading fine. But the Websites store isn't populated, and when I breakpoint on the Clients store on.load function, to see what it's populated with, the Client store is only populated with the client data, but in the raw property for that store, I can see all the websites data. So it's being returned correctly, but my extjs isn't correct. Here are the stores:
Client Store
Ext.define('APP.store.Clients', {
extend: 'Ext.data.Store',
autoLoad: false,
model: 'APP.model.Client',
proxy: {
type: 'ajax',
url: '/client/list',
reader: {
type: 'json',
root: 'items'
}
},
sorters: [{
property: 'name',
direction: 'ASC'
}]
});
Websites Store
Ext.define('APP.store.Websites', {
extend: 'Ext.data.Store',
requires: ['Ext.ux.Msg'],
autoLoad: false,
model: 'APP.model.Website',
proxy: {
type: 'ajax',
url: '/client/list',
reader: {
type: 'json',
root: 'items'
},
writer: {
type: 'json'
}
},
sorters: [{
property: 'sub_domain',
direction: 'ASC'
}]
});
My final result is...I would like to populate both stores so I can click on an element, and when it loads something from the parent store, I can access the child store(s) (there will be more when I figure out this problem) to populate a couple grid(s) in tabs.
What am I missing as far as my setup? I just downloaded extjs4 a couple days ago, so I am on 4.1.
Put your proxies in your models, unless you have a good reason not to [1]
Make sure you require the related model(s), either in the same file, or earlier in the application
Use foreignKey if you want to load the related data at will (i.e. with a later network request).
Use associationKey if the related data is loaded in the same (nested) response
Or just use both
Always name your relationships (otherwise the name will be weird if using namespaces).
Always use the fully qualified model name for the model property in your relationships
Working code:
model/Contact.js:
Ext.define('Assoc.model.Contact', {
extend:'Ext.data.Model',
requires:[
'Assoc.model.PhoneNumber'
],
fields:[
'name' /* automatically has an 'id' field */
],
hasMany:[
{
model:'Assoc.model.PhoneNumber', /*use the fully-qualified name here*/
name:'phoneNumbers',
foreignKey:'contact_id',
associationKey:'phoneNumbers'
}
],
proxy:{
type:'ajax',
url:'assoc/data/contacts.json',
reader:{
type:'json',
root:'data'
}
}
});
model/PhoneNumber.js:
Ext.define('Assoc.model.PhoneNumber', {
extend:'Ext.data.Model',
fields:[
'number',
'contact_id'
],
proxy:{
type:'ajax',
url:'assoc/data/phone-numbers.json',
reader:{
type:'json',
root:'data'
}
}
});
data/contacts.json:
{
"data":[
{
"id":1,
"name":"neil",
"phoneNumbers":[
{
"id":999,
"contact_id":1,
"number":"9005551234"
}
]
}
]
}
data/phone-numbers.json
{
"data":[
{
"id":7,
"contact_id":1,
"number":"6045551212"
},
{
"id":88,
"contact_id":1,
"number":"8009996541"
},
]
}
app.js:
Ext.Loader.setConfig({
enabled:true
});
Ext.application({
requires:[
'Assoc.model.Contact'
],
name:'Assoc',
appFolder:'Assoc',
launch:function(){
/* load child models that are in the response (uses associationKey): */
Assoc.model.Contact.load(1, {
success: function(record){
console.log(record.phoneNumbers());
}
});
/* load child models at will (uses foreignKey). this overwrites child model that are in the first load response */
Assoc.model.Contact.load(1, {
success: function(record){
record.phoneNumbers().load({
callback:function(){
console.log(arguments);
}
});
}
});
}
});
[1] A store will use its model's proxy. You can always override the store's proxy if need be. You won't be able to use Model.load() if the model has no proxy.
Your assumption is wrong. You expect that the WebSite store loads itself but that is not how it works. What you can do is the following (this is untested but is how I do it in all my projects):
In your clients grid add a listener for the itemclick event to call the following method (showWebSites). It will receive the selected client record containing the selected APP.model.Client instance. Then, and given that each client has a set of WebSites, the method will load the APP.store.Websites store and the client´s websites will be displayed in your view.
showWebSites: function (sender, selectedClient) {
Ext.StoreManager.lookup('APP.store.Websites')
.loadData(selectedClient.data.WebSites);
}
This is the way. I hope you find it useful.