Grids with store and viewmodel in Extjs-5.1.2 - extjs

I'm just getting started with ExtJS and am looking to create a grid which will populate based on data it will receive from the server as a json. I'm having trouble understanding the architecture and how to separate information to correctly display on the grid view, since it seems much more complex and involved than if I were working in something like vanilla Javascript.
I currently have it working when the data has been hardcoded into the view here:
Ext.define('MyApp.view.main.Main', {
extend: 'Ext.container.Viewport',
requires: [
'MyApp.view.main.MainController',
'MyApp.view.main.MainModel',
'Ext.panel.Panel',
'Ext.grid.Panel'
],
/*
...Other containers and panels here
*/
xtype: 'grid',
width: '99%',
flex: 1,
store: {
fields:['name', 'email', 'address', 'hobby', 'notes'],
data:[
{ name: 'Rate', email: "rate#example.com",
address: "382 Kilmanjaro", hobby: "tennis", notes: "Lorem ipsum dolor.."},
{ name: 'Jimjam', email: "jim#example.com",
address: "889 McKinley", hobby: "none"}
],
proxy: {
type: 'memory'
}
},
columns: [
{ text: 'Name', dataIndex: 'name' },
{ text: 'Email', dataIndex: 'email'},
{ text: 'address', dataIndex: 'address' },
{ text: 'hobby', dataIndex: 'hobby'},
{ text: 'Notes', dataIndex: 'notes', flex: 1, cellWrap: true}
]
}]
But I'd like to move the store and data out of the view, and ideally read from a json file (and later a GET request). Other questions I've seen have had a bunch of different methods to approach this but I find them all rather confusing and none seem to work for me, especially with different versions (I'm using ExtJS 5.1.2).
I'd appreciate any pointers in the right direction as to where to place my store and data and how to bind it correctly to the view. I think my main issues lie with usage of the associated Controller.js, Model.js, and Store.js files, and what kind of information goes in them.

Just updating that I got it working! I checked out the kitchen sink examples and old documentation from ExtJS4 to understand how the architecture worked.
My main issue was being new to ExtJS and attempting to create something while learning it at the same time, so I hope any other newbies can wrap their heads around this!
The basic idea is that there is a Model, Store, and View, and I've understood it as a Model being a class, a Store being a Collection of those objects, and the view being a display. A Store depends on a Model, and a View (at least, the grid I was building) depends on a Store. I tried to avoid separating too many views, writing javascript functions and overriding initComponent, but this was exactly what I had to do to get it working.
So I've written architecture like this:
model/Person.js:
Ext.define('MyApp.model.Person', {
extend: 'Ext.data.Model',
fields:['name', 'email', 'address', 'hobby', 'notes'],
proxy: {
type: 'ajax',
url: 'data/UserResponse.json',
reader: {
type: 'json',
rootProperty: 'results',
}
}
store/Persons.js:
Ext.define('MyApp.store.Persons', {
extend: 'Ext.data.Store',
/*Model for the store*/
requires: 'MyApp.model.Person',
model: 'MyApp.model.Person'
});
view/main/Main.js:
Ext.define('MyApp.view.main.Main', {
extend: 'Ext.container.Viewport',
requires: [
'Ext.panel.Panel',
'Ext.grid.Panel',
'MyApp.view.main.PersonGrid'
],
xtype: 'container',
controller: 'main',
viewModel: {
type: 'main'
},
layout: {
type: 'vbox',
align: 'center'
},
width: '100%',
items:
[{
xtype: 'panel',
flex: 1,
border: false,
width: '98%',
layout: {
type: 'vbox',
align: 'stretch'
},
items:
[{
xtype: 'panel',
height: 30,
margin: '5 5 1 5',
flex: 1,
width: '98%',
layout: {
type: 'vbox',
align: 'center'
},
title: 'Users - USA',
collapsible: true,
items: [
{
xtype: 'person-grid',
rootVisible: true,
store: 'Persons'
}]
}]
}]
});
view/main/PersonGrid.js:
Ext.define('MyApp.view.main.PersonGrid', {
extend: 'Ext.grid.Panel',
alias: 'widget.person-grid',
xtype: 'grid',
width: '99%',
flex: 1,
columns: [
{ text: 'Name', dataIndex: 'name' },
{ text: 'Email', dataIndex: 'email'},
{ text: 'address', dataIndex: 'address' },
{ text: 'hobby', dataIndex: 'hobby'},
{ text: 'Notes', dataIndex: 'notes', flex: 1, cellWrap: true}
],
initComponent: function() {
//initComponent taken from http://examples.sencha.com/extjs/5.1.0/examples/kitchensink/#xml-grid
var me = this;
this.callParent();
this.on('afterlayout', this.loadStore, this, {
delay: 1,
single: true
});
},
loadStore: function() {
this.getStore().load();
}
});
data/UserResponse.json:
{
"success": true,
"results":[
{ name: 'Rate', email: "rate#example.com",
address: "382 Kilmanjaro", hobby: "tennis", notes: "Lorem ipsum dolor.."},
{ name: 'Jimjam', email: "jim#example.com",
address: "889 McKinley", hobby: "none"}
]
}
app/Application.js:
Ext.define('MyApp.Application', {
extend: 'Ext.app.Application',
models: ['Person'],
stores: ['Persons'],
launch: function () {
// TODO - Launch the application
}
});
The main difference from what I was attempting before was to separate the grid into its own view, and edit the initComponent function here to load the store (which I wasn't doing earlier)! Earlier, I had the grid and its configurations nested within the items{} array of other components and trying to autoLoad the store, or utilize storeId and other methods of binding store to grid.
The above architecture works perfectly for me, and I am able to read data from my json file and mock responses from server! :)

Related

ExtJS Grid Not Displaying Rest Data

I have created a mini EXTJS app, defined a model, store, and grid. I have verified the store is working because I can use Chrome debug tools to create an instance and view the data. However, when I attempt to display the data in a grid it never shows.
Here is the code for the app.js:
Ext.application({
launch: function () {
Ext.define('Companies', {
extend: 'Ext.data.Model',
fields: ['id', 'link', 'name']
});
Ext.define('CompaniesStore', {
extend: 'Ext.data.Store',
model: 'Companies',
storeId: 'cStore',
autoLoad: true,
proxy: {
type: 'rest',
url: 'http://corky52547.somee.com/Service1.svc/Companies'
}
});
Ext.create('Ext.container.Viewport', {
name : "viewPort2",
layout: 'fit',
renderTo: Ext.getBody(),
items: [
{
title: 'Bill Reminder Web'
},
{
xtype: 'grid',
title: 'Bills',
height: 100,
width: 100,
columns: [
{text: 'ID', width: 100, dataIndex: 'id'},
{text: 'Link', flex: 1, dataIndex: 'link'},
{text: 'Name', width: 200, dataIndex: 'name'}
],
store: Ext.create('CompaniesStore',{})
}
]
});
}
});
Update:
I am now able to get the data to display but like this with no theme. How do I update the theme?
CORS (Cross Origin requests) is blocking the request to domain.
Layout Fit is also causing some issues.
To get CORS working, you will need to add
Access-Control-Allow-Origin: *
Otherwise, you can use middle-ware proxy like cors-anywhere
Minor mistakes like field names should be matched with exact case they are available in response.
Here is a working POC Code:
Ext.application({
name: "Application",
launch: function () {
Ext.define('Companies', {
extend: 'Ext.data.Model',
fields: ['ID', 'Link', 'Name']
});
Ext.define('CompaniesStore', {
extend: 'Ext.data.Store',
model: 'Companies',
storeId: 'cStore',
autoLoad: true,
proxy: {
type: 'rest',
url: 'https://cors-anywhere.herokuapp.com/http://corky52547.somee.com/Service1.svc/Companies'
}
});
Ext.create('Ext.container.Viewport', {
name: "viewPort2",
renderTo: Ext.getBody(),
items: [{
title: 'Bill Reminder Web'
}, {
xtype: 'grid',
title: 'Bills',
flex: 1,
columns: [{
text: 'ID',
width: 100,
dataIndex: 'ID'
}, {
text: 'Link',
flex: 1,
dataIndex: 'Link'
}, {
text: 'Name',
width: 200,
dataIndex: 'Name'
}],
store: Ext.create('CompaniesStore', {})
}]
});
}
});
Here is working fiddle: https://fiddle.sencha.com/#view/editor&fiddle/2gbc

Extjs textfield binding

I am trying to find the best way to bind items to a textfield in my extjs project. I downloaded the data into a store with one record in the controller. How would I bind to this textfield from the one record? I would preferably bind in the view, not in the controller
You should read this guide to understand better what binding is
The best solution for you is bind the record on the viewmodel of the view, so:
textfield.setBind({
value:'{myRec.valueToRefer}'
})
viewmodel.set('myRec',record.getData());
If you want, you can also use a form to handle this, using form.loadRecord and giving to the textfield a name..
A tip:
set inside the viewmodel a value to null:
data:{
myRec:null
}
Set your record in the viewmodel after setting the bind to the textfield.
Other tip:
If you can, avoid using setBind and prefer to set the binding directly on textfield creation:
//WILL WORK BUT YOU CAN AVOID IT
textfield.setBind({
value:'{myRec.valueToBind}'
})
//YES
var textfield=Ext.create({
xtype:'textfield',
bind:{
value:'{myRec.valueToBind}'
}
});
Refer to Sencha documentation also
You can use bind config to bind the data or any other config for ExtJS component.
Bind setting this config option adds or removes data bindings for other configs.
For example, to bind the title config:
var panel = Ext.create({
xtype: 'panel',
bind: {
title: 'Hello {user.name}'
}
});
To dynamically add bindings:
panel.setBind({
title: 'Greetings {user.name}!'
});
To remove bindings:
panel.setBind({
title: null
});
In this FIDDLE, I have created a demo for biding. I hope this will help/guide you to achieve you requirement.
CODE SNIPPET
Ext.application({
name: 'Fiddle',
launch: function () {
//defining Store
Ext.define('Store', {
extend: 'Ext.data.Store',
alias: 'store.gridstore',
autoLoad: true,
fields: ['name', 'email', 'phone'],
proxy: {
type: 'ajax',
url: 'data1.json',
reader: {
type: 'json',
rootProperty: ''
}
}
});
//defining view model
Ext.define('MyViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.myvm',
data: {
formdata: null
},
stores: {
gridstore: {
type: 'gridstore'
}
}
});
//Controller
Ext.define('MyViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.myview',
onGridItemClick: function (grid, record) {
//Bind the form data for CLICKED record
this.getViewModel().set('formdata', record)
}
});
//creating panel with GRID and FORM
Ext.create({
xtype: 'panel',
controller: 'myview',
title: 'Binding Example',
renderTo: Ext.getBody(),
viewModel: {
type: 'myvm'
},
layout: 'vbox',
items: [{
xtype: 'grid',
flex: 1,
width: '100%',
bind: '{gridstore}',
columns: [{
text: 'Name',
dataIndex: 'name'
}, {
text: 'Email',
dataIndex: 'email',
flex: 1
}, {
text: 'Phone',
dataIndex: 'phone'
}],
listeners: {
itemclick: 'onGridItemClick'
}
}, {
xtype: 'form',
flex: 1,
width: '100%',
defaults: {
anchor: '100%'
},
title: 'Bind this form on Grid item Click',
bodyPadding:15,
margin: '20 0 0 0',
// The fields
defaultType: 'textfield',
items: [{
fieldLabel: 'Name',
name: 'first',
allowBlank: false,
bind: '{formdata.name}'
}, {
fieldLabel: 'Email',
name: 'email',
allowBlank: false,
bind: '{formdata.email}'
}, {
fieldLabel: 'Phone',
name: 'phone',
allowBlank: false,
bind: '{formdata.phone}'
}]
}]
});
}
});

EXTJS creating custom tree panel

I am using EXTJS and I want to create my TreePanel with one more icon to the right that will be set to indicate something has changed. I am struggling to do this as I am unsure where I can modify these properties. It would be easy with HTML alone but obviously this framework is useful and I need to integrate these changes into it.
Thanks,
Dan
Yes, there is way to handle it in ExtJS framework with renderer method on column.
Here is how I would achieve it using ExtJS 6.0.x:
Ext.create('Ext.container.Viewport', {
layout: 'fit',
items: [{
xtype: 'container',
scrollable: 'y',
flex: 1,
layout: {
type: 'hbox',
alignt: 'stretchmax'
},
items: [{
xtype: 'treepanel',
rootVisible: false,
flex: 1,
store: {
type: 'tree',
parentIdProperty: 'parentId',
root: {
text: 'Navigation Bar',
expanded: true,
children: [{
text: 'Parts',
children: [{
leaf: true,
itemId: 'addPart',
text: 'Add new part',
changed: true
}, {
leaf: true,
itemId: 'manageParts',
text: 'Manage Parts',
changed: false
}, ]
}, {
leaf: true,
itemId: 'announcements',
text: 'Announcements',
changed: true
}]
}
},
columns: [{
text: 'Code',
dataIndex: 'codigo',
align: 'left'
}, {
xtype: 'treecolumn',
text: 'Description',
dataIndex: 'text',
flex: 1
}, {
text: 'Edited',
iconCls: 'x-fa fa-edit',
align: 'center',
flex: 1,
renderer: function (f, grid, record) {
if(record.get('changed') === true) {
return "Changed Icon here"
}
else {
return "Not changed icon here";
}
}
}]
}]
}]
});
With this you can easily manage the data with store and can easily update the data in store. ExtJS will take care of rendering the given configuration for columns.
You can also use actioncolumn to show icons/buttons and handle events with listeners.
Example Fiddle: https://fiddle.sencha.com/#view/editor&fiddle/28qm
I Have solved the problem. You can embed html into the text field in the store.
Ext.define('MyApp.store.Navigation', {
extend: 'Ext.data.TreeStore',
alias: 'store.navigation',
rootData: {
text: 'Navigation Bar',
expanded: true,
children: [
{
text: 'Parts',
children: [
{ leaf:true, itemId:'addPart', text: 'Add new part' },
{ leaf:true, itemId:'manageParts', text: 'Manage Parts <b>here</b>' },
]
},
{
leaf:true, itemId:'announcements', text: 'Announcements'
}
]
},
constructor: function (config) {
// Since records claim the data object given to them, clone the data
// for each instance.
config = Ext.apply({
root: Ext.clone(this.rootData)
}, config);
this.callParent([config]);
}
});
Probably a poor solution but gets the job done.

ext js multiple instances of same grid

I'm having an issue with multiple instances of an ext js grid all showing the same data. I am using Ext js 4.1.1.
I have a main tab panel. In that panel, there are multiple people tabs. Inside each person tab is a details tab and a family tab.
The details tab is a simple form with text boxes, combo boxes, etc. The family tab has both a dataview and a grid.
If only one person tab is open at a time, everything works fine. As soon as a second person is opened, the family tabs look the same (both the dataview and the grid). It seems to me that the problem has something to do with the model. Perhaps they are sharing the same instance of the model, and that is causing one refresh to change all the data. The dataview and the grid both have the same problem, but I think that if I can fix the problem with the grid, then I can apply the same logic to fix the dataview. I will leave the code for the dataview out of this question unless it becomes relevant.
PersonTab.js
Ext.require('Client.view.MainTab.PersonDetailsForm');
Ext.require('Client.view.MainTab.PersonFamilyForm');
Ext.require('Client.view.MainTab.EventForm');
Ext.define('Client.view.MainTab.PersonTab',
{
extend: 'Ext.tab.Panel',
waitMsgTarget: true,
alias: 'widget.MainTabPersonTab',
layout: 'fit',
activeTab: 0,
tabPosition: 'bottom',
items:
[
{
title: 'Details',
closable: false,
xtype: 'MainTabPersonDetailsForm'
},
{
title: 'Family',
closable: false,
xtype: 'MainTabPersonFamilyForm'
},
{
title: 'page 3',
closable: false,
xtype: 'MainTabEventForm'
}
]
});
MainTabPersonFamilyForm.js
Ext.require('Client.view.MainTab.PersonFamilyHeadOfHouseholdDataView');
Ext.require('Client.view.MainTab.PersonFamilyGrid');
Ext.define('Client.view.MainTab.PersonFamilyForm',
{
extend: 'Ext.form.Panel',
alias: 'widget.MainTabPersonFamilyForm',
waitMsgTarget: true,
padding: '5 0 0 0',
autoScroll: true,
items:
[
{
xtype: 'displayfield',
name: 'HeadOfHouseholdLabel',
value: 'The head of my household is:'
},
{
xtype: 'MainTabPersonFamilyHeadOfHouseholdDataView'
},
{
xtype: 'checkboxfield',
boxLabel: "Use my Head of Household's address as my address",
boxLabelAlign: 'after',
inputValue: true,
name: 'UseHeadOfHouseholdAddress',
allowBlank: true,
padding: '0 20 5 0'
},
{
xtype: 'MainTabPersonFamilyGrid'
}
],
config:
{
idPerson: ''
}
});
MainTabPersonFamilyGrid.js
Ext.require('Client.store.PersonFamilyGrid');
Ext.require('Ext.ux.CheckColumn');
Ext.define('Client.view.MainTab.PersonFamilyGrid',
{
extend: 'Ext.grid.Panel',
alias: 'widget.MainTabPersonFamilyGrid',
waitMsgTarget: true,
padding: '5 0 0 0',
xtype: 'grid',
title: 'My Family Members',
store: Ext.create('Client.store.PersonFamilyGrid'),
plugins: Ext.create('Ext.grid.plugin.CellEditing'),
viewConfig:
{
plugins:
{
ptype: 'gridviewdragdrop',
dragGroup: 'PersonFamilyGridTrash'
}
},
columns:
[
{ text: 'Name', dataIndex: 'Name'},
{ text: 'Relationship', dataIndex: 'Relationship', editor: { xtype: 'combobox', allowblank: true, displayField: 'display', valueField: 'value', editable: false, store: Ext.create('Client.store.Gender') }},
{ xtype: 'checkcolumn', text: 'Is My Guardian', dataIndex: 'IsMyGuardian', editor: { xtype: 'checkboxfield', allowBlank: true, inputValue: true }},
{ xtype: 'checkcolumn', text: 'I Am Guardian', dataIndex: 'IAmGuardian', editor: { xtype: 'checkboxfield', allowBlank: true, inputValue: true } }
],
height: 200,
width: 400,
buttons:
[
{
xtype: 'button',
cls: 'trash-btn',
iconCls: 'trash-icon-large',
width: 64,
height: 64,
action: 'trash'
}
]
});
PersonFamilyGrid.js (store)
Ext.require('Client.model.PersonFamilyGrid');
Ext.define('Client.store.PersonFamilyGrid',
{
extend: 'Ext.data.Store',
autoLoad: false,
model: 'Client.model.PersonFamilyGrid',
proxy:
{
type: 'ajax',
url: '/Person/GetFamily',
reader:
{
type: 'json'
}
}
});
PersonFamilyGrid.js (model)
Ext.define('Client.model.PersonFamilyGrid',
{
extend: 'Ext.data.Model',
fields:
[
'idFamily',
'idPerson',
'idFamilyMember',
'Name',
'Relationship',
'IsMyGuardian',
'IAmGuardian'
]
});
relevant code from the controller:
....
....
var personTab = thisController.getMainTabPanel().add({
xtype: 'MainTabPersonTab',
title: dropData.data['Title'],
closable: true,
layout: 'fit',
tabpanelid: dropData.data['ID'],
tabpaneltype: dropData.data['Type']
});
personTab.items.items[0].idPerson = dropData.data['ID'];
personTab.items.items[1].idPerson = dropData.data['ID'];
thisController.getMainTabPanel().setActiveTab(personTab);
....
....
You're setting the store as a property on your grid prototype and creating it once at class definition time. That means that all your grids instantiated from that class will share the exact same store.
Note that you're also creating a single cellediting plugin that will be shared with all instantiations of that grid as well. That definitely won't work. You likely will only be able to edit in either the first or last grid that was instantiated.
In general you should not be setting properties like store, plugins, viewConfig or columns on the class prototype. Instead you should override initComponent and set them inside that method so that each instantiation of your grid gets a unique copy of those properties.
Ext.define('Client.view.MainTab.PersonFamilyGrid', {
extend: 'Ext.grid.Panel',
alias: 'widget.MainTabPersonFamilyGrid',
waitMsgTarget: true,
padding: '5 0 0 0',
title: 'My Family Members',
height: 200,
width: 400
initComponent: function() {
Ext.apply(this, {
// Each grid will create its own store now when it is initialized.
store: Ext.create('Client.store.PersonFamilyGrid'),
// you may need to add the plugin in the config for this
// grid
plugins: Ext.create('Ext.grid.plugin.CellEditing'),
viewConfig: {
plugins: {
ptype: 'gridviewdragdrop',
dragGroup: 'PersonFamilyGridTrash'
}
},
columns: /* ... */
});
this.callParent(arguments);
}
});
It's hard to tell exactly, but from the code you have submitted it appears that you are not setting the id parameter on your tabs and your stores, which causes DOM collisions as the id is used to make a component globally unique. This has caused me grief in the past when sub-classing components (such as tabs and stores) and using multiple instances of those classes.
Try giving each one a unique identifier (such as the person id) and then referencing them using that id:
var personTab = thisController.getMainTabPanel().add({
id: 'cmp-person-id',
xtype: 'MainTabPersonTab',
...
store: Ext.create('Client.store.PersonFamilyGrid',
{
id: 'store-person-id',
...
});
Ext.getCmp('cmp-person-id');
Ext.StoreManager.lookup('store-person-id');
Hope that helps.

infinite scroll not rendering

I'm trying to use ExtJS4.1 infinite scroll feature.
The ajax calls are being made, the data returned, but only the first page loads.
What am I doing wrong? When I scroll down nothing happens.
My code:
Store:
Ext.define('BM.store.Tests', {
extend: 'Ext.data.Store',
model: 'BM.model.Test',
storeId: 'Tests',
buffered: true,
leadingBufferZone: 50,
pageSize: 25,
purgePageCount: 0,
autoLoad: true
});
The proxy is in the model:
proxy: {
type: 'ajax',
api: {
create: '../webapp/tests/create',
read: '../webapp/tests',
update: '../webapp/tests/update'
},
reader: {
type: 'json',
root: 'tests',
successProperty: 'success'
}
}
The grid:
Ext.define('BM.view.test.MacroList', {
extend: 'Ext.grid.Panel',
alias:'widget.macro-test-list',
store: 'Tests',
// loadMask: true,
// selModel: {
// pruneRemoved: false
// },
// viewConfig: {
// trackOver: false
// },
verticalScroller: {
numFromEdge: 5,
trailingBufferZone: 10,
leadingBufferZone: 20
},
initComponent: function() {
this.columns = [
{
xtype: 'gridcolumn',
dataIndex: 'name',
text: 'Name'
},
{
xtype: 'datecolumn',
dataIndex: 'created',
text: 'Date Created',
format: 'd-M-Y'
},
{
xtype: 'datecolumn',
dataIndex: 'changed',
text: 'Last Updated',
format: 'd-M-Y'
}
];
this.callParent(arguments);
}
The only thing that's different between my implementation and the one in the examples, is that my grid is not rendered to the body.
The viewport contains a border layout.
The grid is part of the west region panel:
{
collapsible: true,
region: 'west',
xtype: 'macro',
width: 500
}
The macro panel:
Ext.define('BM.view.Macro', {
extend: 'Ext.panel.Panel',
alias: 'widget.macro',
title: 'Tests',
layout: {
type: 'vbox',
align: 'stretch'
},
items: [
{
id: "macro-test-list-id",
xtype: 'macro-test-list',
flex: 1
},
{
id: "macro-report-panel-id",
xtype: 'macro-report-list',
title: false,
flex: 1
},
{
id: "macro-report-list-id-all",
xtype: 'macro-report-list-all',
flex: 1,
hidden: true,
layout: 'anchor'
}
]
});
I've tried many many things, changing layouts, giving the grid a fixed height, etc...
Nothing works, scroll down, and the grid doesn't refresh.
One other piece of information: The DB contains 53 records of data. I'm getting the 3 ajax calls, but only the first 25 records appear (as I requested).
Any thoughts?
It sounds like you might be forgetting to put the total property in the server's JSON response. Does your answer have a structure like this:
{
"total": 53,
"tests": [(...)]
}
The totalProperty name is defined in the Reader configuration and defaults to total.
http://docs.sencha.com/ext-js/4-1/#!/api/Ext.data.reader.Reader-cfg-totalProperty
You need to upgrade to ExtJS 4.2!

Resources