Exemplo ExtJS 4 MVC: Complex DashBoard (Grid, Form e Gráficos)

Mais um exemplo MVC de ExtJS 4 aqui no blog. Hoje vamos ver o código do exemplo Complex Dashboard.

Vamos lá!

Estrutura do Projeto:

Model - Stock

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
Ext.define('ExtMVC.model.Stock', {
extend: 'Ext.data.Model',
fields: [
{name: 'company'},
{name: 'price', type: 'float'},
{name: 'revenue %', type: 'float'},
{name: 'growth %', type: 'float'},
{name: 'product %', type: 'float'},
{name: 'market %', type: 'float'}
]
});
[/code]

Model - RadarDataSet

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
Ext.define('ExtMVC.model.RadarDataSet', {
extend: 'Ext.data.Model',
fields: ['Name', 'Data']
});
[/code]

Store - Stock

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
Ext.define('ExtMVC.store.Stocks', {
extend: 'Ext.data.ArrayStore',
model: 'ExtMVC.model.Stock'
});
[/code]

Store - RadarDataSet

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
Ext.define('ExtMVC.store.RadarDataSets', {
extend: 'Ext.data.ArrayStore',
model: 'ExtMVC.model.RadarDataSet',

proxy: {
type: 'memory',
reader: {
type: 'json'
}
},

data: [
{
'Name': 'Price',
'Data': 100
}, {
'Name': 'Revenue %',
'Data': 100
}, {
'Name': 'Growth %',
'Data': 100
}, {
'Name': 'Product %',
'Data': 100
}, {
'Name': 'Market %',
'Data': 100
}]
});
[/code]

View - chart - StockBar

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
Ext.define('ExtMVC.view.chart.StockBar', {
extend: 'Ext.chart.Chart',
alias : 'widget.stockbar',

flex: 1,
shadow: true,
animate: true,
store: 'Stocks',
axes: [{
type: 'Numeric',
position: 'left',
fields: ['price'],
minimum: 0,
hidden: true
}, {
type: 'Category',
position: 'bottom',
fields: ['company'],
label: {
renderer: function(v) {
return Ext.String.ellipsis(v, 15, false);
},
font: '9px Arial',
rotate: {
degrees: 270
}
}
}],
series: [{
type: 'column',
axis: 'left',
highlight: true,
style: {
fill: '#456d9f'
},
highlightCfg: {
fill: '#a2b5ca'
},
label: {
contrast: true,
display: 'insideEnd',
field: 'price',
color: '#000',
orientation: 'vertical',
'text-anchor': 'middle'
},
xField: 'name',
yField: ['price']
}]
});
[/code]

View - chart - StockRadar

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
Ext.define('ExtMVC.view.chart.StockRadar', {
extend: 'Ext.chart.Chart',
alias : 'widget.stockradar',

margin: '0 0 0 0',
insetPadding: 20,
flex: 1.2,
animate: true,
store: 'RadarDataSets',
theme: 'Blue',
axes: [{
steps: 5,
type: 'Radial',
position: 'radial',
maximum: 100
}],
series: [{
type: 'radar',
xField: 'Name',
yField: 'Data',
showInLegend: false,
showMarkers: true,
markerConfig: {
radius: 4,
size: 4,
fill: 'rgb(69,109,159)'
},
style: {
fill: 'rgb(194,214,240)',
opacity: 0.5,
'stroke-width': 0.5
}
}]
});
[/code]

View - stocks- StockGrid

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
Ext.define('ExtMVC.view.stocks.StockGrid', {
extend: 'Ext.grid.Panel',
alias : 'widget.stockgrid',

id: 'company-form',
flex: 0.60,
store: 'Stocks',
title:'Company Data',

perc: function(v) {
return v + '%';
},

initComponent: function() {

this.columns= [
{
id :'company',
text : 'Company',
flex: 1,
sortable : true,
dataIndex: 'company'
},
{
text : 'Price',
width : 75,
sortable : true,
dataIndex: 'price',
align: 'right',
renderer : 'usMoney'
},
{
text : 'Revenue',
width : 75,
sortable : true,
align: 'right',
dataIndex: 'revenue %',
renderer: this.perc
},
{
text : 'Growth',
width : 75,
sortable : true,
align: 'right',
dataIndex: 'growth %',
renderer: this.perc
},
{
text : 'Product',
width : 75,
sortable : true,
align: 'right',
dataIndex: 'product %',
renderer: this.perc
},
{
text : 'Market',
width : 75,
sortable : true,
align: 'right',
dataIndex: 'market %',
renderer: this.perc
}
];

this.callParent(arguments);
}
});
[/code]

View - stocks- StockForm

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
Ext.define('ExtMVC.view.stocks.StockForm', {
extend: 'Ext.form.Panel',
alias : 'widget.stockform',

requires: [
'ExtMVC.view.stocks.StockGrid',
'ExtMVC.view.chart.StockBar',
'ExtMVC.view.chart.StockRadar'
],

title: 'Company data',
frame: true,
bodyPadding: 5,
//width: 870,
//height: 720,

fieldDefaults: {
labelAlign: 'left',
msgTarget: 'side'
},

layout: {
type: 'vbox',
align: 'stretch'
},

items: [
{
height: 200,
layout: 'fit',
margin: '0 0 3 0',
items: [{ xtype: 'stockbar'}]
},
{

layout: {type: 'hbox', align: 'stretch'},
flex: 3,
border: false,
bodyStyle: 'background-color: transparent',

items: [{
xtype: 'stockgrid'
}, {
flex: 0.4,
layout: {
type: 'vbox',
align:'stretch'
},
margin: '0 0 0 5',
title: 'Company Details',
items: [{
margin: '5',
xtype: 'fieldset',
flex: 1,
title:'Company details',
defaults: {
width: 240,
labelWidth: 90,
disabled: true
},
defaultType: 'numberfield',
items: [{
fieldLabel: 'Name',
name: 'company',
xtype: 'textfield'
},{
fieldLabel: 'Price',
name: 'price',
maxValue: 100,
minValue: 0,
enforceMaxLength: true,
maxLength: 5
},{
fieldLabel: 'Revenue %',
name: 'revenue %',
maxValue: 100,
minValue: 0,
enforceMaxLength: true,
maxLength: 5
},{
fieldLabel: 'Growth %',
name: 'growth %',
maxValue: 100,
minValue: 0,
enforceMaxLength: true,
maxLength: 5
},{
fieldLabel: 'Product %',
name: 'product %',
maxValue: 100,
minValue: 0,
enforceMaxLength: true,
maxLength: 5
},{
fieldLabel: 'Market %',
name: 'market %',
maxValue: 100,
minValue: 0,
enforceMaxLength: true,
maxLength: 5
}]
}, {xtype:'stockradar'}]
}]
}]
});
[/code]

ViewPort

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
/**
* The main application viewport, which displays the whole application
* @extends Ext.Viewport
*/
Ext.define('ExtMVC.view.Viewport', {
extend: 'Ext.Viewport',

layout: 'fit',

requires: [
'ExtMVC.view.stocks.StockForm',
'ExtMVC.view.stocks.StockGrid',
'ExtMVC.view.chart.StockBar',
'ExtMVC.view.chart.StockRadar'
],

initComponent: function() {
var me = this;

Ext.apply(me, {
items: [
{
xtype: 'stockform'
}
]
});

me.callParent(arguments);
}
});
[/code]

Controller

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
Ext.define('ExtMVC.controller.Stocks', {
extend: 'Ext.app.Controller',

models: ['Stock','RadarDataSet'],

stores: ['Stocks', 'RadarDataSets'],

views: [
'chart.StockRadar',
'chart.StockBar',
'stocks.StockGrid',
'stocks.StockForm'
],

refs: [{
ref: 'stockForm',
selector: 'form'
},{
ref: 'stockGrid',
selector: 'grid'
}],

init: function() {

this.loadStore();

this.control({
'stockgrid': {
selectionchange: this.selectionchange
}/*,
'textfield': {
change: this.changeField
}*/,
'numberfield': {
change: this.changeField
},
'stockbar': {
afterrender: function (chart,o) {

var series = chart.series.getAt(0);
series.listeners = {
itemmouseup: function(item) {

var series = Ext.ComponentQuery.query('stockbar')[0].series.get(0);
var index = Ext.Array.indexOf(series.items, item);
var selectionModel = Ext.ComponentQuery.query('grid')[0].getSelectionModel();

var selectedStoreItem = item.storeItem;
selectionModel.select(index);
}
}
},
beforerefresh: this.beforerefresh
}
});
},

loadStore: function(){

var store = this.getStocksStore();

// sample static data for the store
var myData = [
['3m Co'],
['Alcoa Inc'],
['Altria Group Inc'],
['American Express Company'],
['American International Group, Inc.'],
['AT&T Inc'],
['Boeing Co.'],
['Caterpillar Inc.'],
['Citigroup, Inc.'],
['E.I. du Pont de Nemours and Company'],
['Exxon Mobil Corp'],
['General Electric Company'],
['General Motors Corporation'],
['Hewlett-Packard Co'],
['Honeywell Intl Inc'],
['Intel Corporation'],
['International Business Machines'],
['Johnson & Johnson'],
['JP Morgan & Chase & Co'],
['McDonald\'s Corporation'],
['Merck & Co., Inc.'],
['Microsoft Corporation'],
['Pfizer Inc'],
['The Coca-Cola Company'],
['The Home Depot, Inc.'],
['The Procter & Gamble Company'],
['United Technologies Corporation'],
['Verizon Communications'],
['Wal-Mart Stores, Inc.']
];

for (var i = 0, l = myData.length, rand = Math.random; i < l; i++) {
var data = myData[i];
data[1] = ((rand() * 10000) >> 0) / 100;
data[2] = ((rand() * 10000) >> 0) / 100;
data[3] = ((rand() * 10000) >> 0) / 100;
data[4] = ((rand() * 10000) >> 0) / 100;
data[5] = ((rand() * 10000) >> 0) / 100;
}

store.loadData(myData);
},

selectionchange: function(model, records){

var json, name, i, l, items, series, fields;

var form = this.getStockForm().getForm();

if (records[0]) {
rec = records[0];

if (!form) {
form = this.getStockForm().getForm();
fields = form.getFields();
fields.each(function(field){
if (field.name != 'company') {
field.setDisabled(false);
}
});
} else {
fields = form.getFields();
}

// prevent change events from firing
fields.each(function(field){
field.suspendEvents();
console.log('suspended');
});
form.loadRecord(rec);
this.updateRecord(rec);

fields.each(function(field){
field.resumeEvents();
});

fields.each(function(field){
if (field.name != 'company') {
field.setDisabled(false);
}
});
}
},

updateRecord: function(rec) {
var name, series, i, l, items, json = [{
'Name': 'Price',
'Data': rec.get('price')
}, {
'Name': 'Revenue %',
'Data': rec.get('revenue %')
}, {
'Name': 'Growth %',
'Data': rec.get('growth %')
}, {
'Name': 'Product %',
'Data': rec.get('product %')
}, {
'Name': 'Market %',
'Data': rec.get('market %')
}];

var store = this.getRadarDataSetsStore();
store.loadData(json);
this.selectItem(rec);
},

selectItem: function(storeItem) {

var name = storeItem.data.company;//storeItem.get('company'),
var series = Ext.ComponentQuery.query('stockbar')[0].series.get(0);
var i, items, l;

series.highlight = true;
series.unHighlightItem();
series.cleanHighlights();
for (i = 0, items = series.items, l = items.length; i < l; i++) {
if (name == items[i].storeItem.get('company')) {
selectedStoreItem = items[i].storeItem;
series.highlightItem(items[i]);
break;
}
}
series.highlight = false;
},

changeField: function(field, newValue, oldValue) {

console.log('changeField');

var form = this.getStockForm().getForm();
var rec = this.getStockGrid().getSelectionModel().getSelection()[0];

if (rec && form) {
if (newValue > field.maxValue) {
field.setValue(field.maxValue);
} else {
form.updateRecord(rec);
this.updateRecord(rec);
}
}
},

beforerefresh: function() {
var timer = false;

return function() {
clearTimeout(timer);

var series = Ext.ComponentQuery.query('stockbar')[0].series.get(0);
var index = Ext.Array.indexOf(series.items, item);
var selectionModel = Ext.ComponentQuery.query('grid')[0].getSelectionModel();
var selectedStoreItem = item.storeItem;

if (selectedStoreItem) {
timer = setTimeout(function() {
this.selectItem(selectedStoreItem);
}, 900);
}
};
}
});
[/code]

App

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
Ext.Loader.setConfig({enabled: true});
Ext.Loader.setPath('Ext.ux', 'app/ux');

/*
* BUG: suspentEvents not honoured in Ext.app.EventBus
*
* note: this fix does not queue events when asked.
*
* http://www.sencha.com/forum/showthread.php?171525
*/
Ext.syncRequire('Ext.app.EventBus');
Ext.override(Ext.app.EventBus, {
constructor: function() {
this.mixins.observable.constructor.call(this);

this.bus = {};

var me = this;
Ext.override(Ext.Component, {
fireEvent: function(ev) {
// [
// --
// if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
// ++
if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false && !this.eventsSuspended) {
// ]
return me.dispatch.call(me, ev, this, arguments);
}
return false;
}
});
}
});

Ext.application({
name: 'ExtMVC',

controllers: [
'Stocks'
],

autoCreateViewport: true
});
[/code]

Página HTML

[code lang="html" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<html>
<head>
<title>Ext JS 4 MVC Examples - loiane.com</title>
<!-- Ext JS Files -->
<link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css">
<script type="text/javascript" src="extjs/ext-all-debug.js"></script>

<!-- App Files -->
<script type="text/javascript" src="app.js"></script>

</head>
<body>
</body>
</html>
[/code]

Download do Código Fonte:

Você pode fazer o download do código fonte completo no meu repositório do github: https://github.com/loiane/extjs4-mvc-complex-dashboard

Demo:

Exemplo funcionando: http://loiane.com/extjs/extjs4-mvc-complex-dashboard

Todos os exemplos ExtJS 4 MVC:

http://www.loiane.com/2012/03/exemplos-sencha-extjs-4-em-mvc/

Até o próximo exemplo! :)