2013년 10월 31일 목요일

동적으로 View 변경하기

refs: [
        {
            ref :  'contentPanel',
            selector: 'ContentPanel'
        }
]


var contentPanel = this.getContentPanel();
contentPanel .removeAll(); //removing existing views.

var usersGrid = Ext.create('MyApp.view.Content.UsersGrid');

contentPanel.add(usersGrid);
contentPanel.doLayout();


ID로 Form 객체 가져오기

Ext.getCmp("my-details").getForm().submit();

Form Field에 Store 연결하기

var store = Ext.create('Ext.data.Store', {
    model: 'data',
    proxy: {
        type: 'ajax',
        url: 'process.ashx',
        reader: {
            type: 'json',
            root: 'data'
        }
    },
    autoLoad: true,
    listeners: {
        load: function() {
            var form = Ext.getCmp('formJobSummary'); 
            form.loadRecord(store.data.first());
        }
    }
});

2013년 10월 17일 목요일

Creating a real-life application with the MVC pattern



1. Our first step is to create our app.js file, which will provide our application's
launch point. We create this file in the root of our application alongside our
index.html. At this stage we will give our application the name BugTracker
and an empty launch function:
Ext.application({
name: 'BugTracker',
launch: function(){
console.log('App Launch');
}
});

2. Next we will define our bug model and store. These will be placed in two files named
Bug.js in the model folder and BugStore.js in the store folder respectively. The
contents of each can be seen as follows:
// model/Bug.js
Ext.define('BugTracker.model.Bug', {
extend: 'Ext.data.Model',
fields: [
'title',
'status',
'description',
'severity'
]
});
// store/BugStore.js
Ext.define('BugTracker.store.BugStore', {
extend: 'Ext.data.Store',
model: 'BugTracker.model.Bug',
data: [ ... ]
});

3. We have created our main model and store, now we want to make the application
aware of them so the framework loads them when required. We do this by adding
stores and models configs to the Ext.application call of app.js. These
configs accept an array of strings that are then fully qualified with the relevant
namespace (for example, MyModel becomes BugTracker.model.MyModel).
Ext.application({
name: 'BugTracker',
models: [
'Bug'
],
stores: [
'BugStore'
],
launch: function(){
console.log('App Launch');
}
});

4. Our next step is to create our views. In our application we have a DataView that
displays our set of bugs, a panel to wrap the DataView, a Form panel to allow us to
edit a bug, a window to house the Form panel when it is displayed, and a Viewport
container. We are going to create each of these views as their own class, extending
the relevant framework class, starting with our BugDataView:
// view/BugDataView.js
Ext.define('BugTracker.view.BugDataView', {
extend: 'Ext.view.View',
alias: 'widget.BugDataView',
config: {
store: Ext.create('BugTracker.store.BugStore'),
tpl: '' +
'
' +

'{title}' +
'{severity}</
span>' +
'{description}' +
'{status}' +
'
' +'
',itemSelector: 'div.bug-wrapper',
emptyText: 'Woo hoo! No Bugs Found!',
deferEmptyText: false
}
});

5. Next we create the BugPanel that will have an instance of the BugDataView within
it. Notice the new action config we have given to each of the toolbar buttons; we will
use these later on to hook up their click events:
// view/BugPanel.js
Ext.define('BugTracker.view.BugPanel', {
extend: 'Ext.panel.Panel',
alias: 'widget.BugPanel',
config: {
title: 'Current Bugs,
height: 500,
width: 580,
layout: 'fit',
style: 'margin: 50;',
tbar: [{
xtype: 'combo',
name: 'status',
width: 200,
labelWidth: 100,
fieldLabel: 'Severity Filter',
store: ['1', '2', '3', '4', '5'],
queryMode: 'local'
}, '-', {
text: 'Sort by Severity',
action: 'sortBySeverity'
}, {
text: 'Open all Bugs',
action: 'openAllBugs'
}, '->', {
text: 'Clear Filter',
action: 'clearFilter'
}],
items: [{
xtype: 'BugDataView'
}]
}
});

6. The Bug form panel is next and we follow the same pattern as the other views and
create it in its own file whose name matches the class name that it contains:
// view/BugForm.js
Ext.define('BugTracker.view.BugForm', {
extend: 'Ext.form.Panel',
alias: 'widget.BugForm',
config: {
border: 0,
items: [{
xtype: 'textfield',
name: 'title',
width: 300,
fieldLabel: 'Title'
}, {
xtype: 'textarea',
name: 'description',
width: 300,
height: 100,
fieldLabel: 'Description'
}, {
xtype: 'numberfield',
name: 'severity',
width: 300,
fieldLabel: 'Severity',
value: 1,
maxValue: 5,
minValue: 1
}, {
xtype: 'combo',
name: 'status',
width: 300,
fieldLabel: 'Status',
store: ['Open', 'In Progress', 'Complete'],
queryMode: 'local'
}]
}
});

7. Now we create a window that contains an instance of the BugForm as well as a
single Save button:
Ext.define('BugTracker.view.BugFormWindow', {
extend: 'Ext.window.Window',
alias: 'widget.BugFormWindow',
config: {
height: 250,
width: 500,
title: 'Edit Bug',
modal: true,
items: [{
xtype: 'BugForm'
}],
closeAction: 'hide',
buttons: [{
text: 'Save',
action: 'saveBug'
}]
}
});

8. Our Viewport container is our final view and is a simple component that extends the
Ext.container.Viewport class and creates an instance of the BugPanel class
within its items collection. To have this class instantiated automatically we also add
the autoCreateViewport: true configuration to our application definition in
app.js:
// view/Viewport.js
Ext.define('BugTracker.view.Viewport', {
extend: 'Ext.container.Viewport',
initComponent: function(){
Ext.apply(this, {
layout: 'fit',
items: [{
xtype: 'BugPanel'
}]
});
this.callParent(arguments);
}
});

9. At this point if we open our index.html page we will see the application displaying
our bugs, but clicking on the buttons or bugs doesn't do anything! To bring it all to
life we need to create a controller, which will tie everything together. Start by creating
a new file called Bugs.js in the controller folder and give it the following skeleton
code. At this point we must also add a controllers config to the application
definition of app.js with a value of ['Bugs'] so the controller is automatically
loaded and initialized:
// controller/Bugs.js
Ext.define('BugTracker.controller.Bugs', {
extend: 'Ext.app.Controller',
views: [
'BugDataView',
'BugPanel',
'BugForm',
'BugFormWindow'
],
init: function(){
console.log('Bugs Controller Init');
}
});

10. Next we use the refs config option to adds some accessor methods for each of the
main components so we can access references to them in our action methods:
refs: [{
ref: 'bugDataView',
selector: 'BugPanel BugDataView'
}, {
ref: 'bugFormPanel',
selector: 'BugFormWindow BugForm'
}, {
ref: 'bugFormWindow',
selector: 'BugFormWindow'
}]

11. We now use the control method (which we introduced in the previous recipe) to
wire up our button clicks to a controller action. We use a simple component query to
target the correct button using the action property that we gave each button and
then hook its click event to a method within the controller:
init: function(){
console.log('Bugs Controller Init');
this.control({
'BugPanel button[action="sortBySeverity"]': {
click: this.onSortBySeverityButtonClick,
scope: this
}
});
},
onSortBySeverityButtonClick: function(btn){
this.getBugDataView().getStore().sort('severity', 'DESC');
}
onSortBySeverityButtonClick: function(btn){
this.getBugDataView().getStore().sort('severity', 'DESC');
}

12. We do this for the remaining buttons, the DataView's itemclick event and the
comboboxes' change event:
init: function(){
console.log('Bugs Controller Init');
this.control({
'BugPanel BugDataView': {
itemclick: this.onBugDataViewItemClick,
scope: this
},
'BugPanel button[action="sortBySeverity"]': {
click: this.onSortBySeverityButtonClick,
scope: this
},
'BugPanel button[action="openAllBugs"]': {
click: this.onOpenAllBugsButtonClick,
scope: this
},
'BugPanel button[action="clearFilter"]': {
click: this.onClearFilterButtonClick,
scope: this
},
'BugPanel combo[name="status"]': {
change: this.onBugStatusComboboxChange,

scope: this
},
'BugFormWindow button[action="saveBug"]': {
click: this.onSaveBugButtonClick,
scope: this
}
});
},
onSaveBugButtonClick: function(btn){
var form = this.getBugFormPanel();
// get the record loaded into the form
var selectedRecord = form.getRecord();
selectedRecord.set(form.getValues());
// refilter
this.getBugDataView().getStore().filter();
this.getBugFormWindow().close();
},

onBugDataViewItemClick: function(view, record, item, index,e){
var win = this.getBugFormWindow();
if(!win){
win = Ext.create('BugTracker.view.BugFormWindow');
}
win.show();
// populate the form with the clicked record
this.getBugFormPanel().loadRecord(record);
},
onSortBySeverityButtonClick: function(btn){
this.getBugDataView().getStore().sort('severity', 'DESC');
},
onOpenAllBugsButtonClick: function(btn){
this.getBugDataView().getStore().each(function(model){
model.set('status', 'Open');
model.commit();
}, this);
},
onClearFilterButtonClick: function(btn){
this.getBugDataView().getStore().clearFilter();
},
onBugStatusComboboxChange: function(combo, value, options){
this.getBugDataView().getStore().clearFilter();
this.getBugDataView().getStore().filter('severity', combo.
getValue());
}


Attaching user interactions to controller actions

Our app.js file contains our Ext.Loader configuration and the application's definition using
the following code:
Ext.Loader.setConfig({
enabled: true
});
Ext.application({
name: 'Cookbook',
autoCreateViewport: true,
launch: function(){
console.log('App Launch');
}
});

Ext.define('Cookbook.view.Viewport', {
extend: 'Ext.container.Viewport',
initComponent: function(){
Ext.apply(this, {
layout: 'fit',
items: [Ext.create('Cookbook.view.LoginForm')]
});
this.callParent(arguments);
}
});

The LoginForm view extends the Ext.form.Panel class and contains a Username and
Password field and a Login button:
Ext.define('Cookbook.view.LoginForm', {
extend: 'Ext.form.Panel',
initComponent: function(){
Ext.apply(this, {
items: [{
xtype: 'textfield',
name: 'Username',
fieldLabel: 'Username'
}, {
xtype: 'textfield',
inputType: 'password',
name: 'Password',
fieldLabel: 'Password'
}, {
xtype: 'button',
text: 'Login',
action: 'login'
}]
});
this.callParent(arguments);
}
});

1. We start by creating a controller that will contain our login logic. We do this by
creating a file in our controller folder called Main.js and define a class called
Cookbook.controller.Main extending from the Ext.app.Controller class:
Ext.define('Cookbook.controller.Main', {
extend: 'Ext.app.Controller',
});

2. Next we add the init method to our controller, which will be executed when the
controller is loaded:
Ext.define('Cookbook.controller.Main', {
extend: 'Ext.app.Controller',
init: function(){
console.log('Main Controller Init');
}
});

3. Add a configuration option called controllers to our application definition, located
in our app.js file. This will tell our application that we have a controller called Main
to load and initialize:
Ext.application({
name: 'Cookbook',
autoCreateViewport: true,
controllers: ['Main'],
launch: function(){
console.log('App Launch');
}
});

4. Now that our controller is being initialized, we can hook up the Login
button's click event to an action. We start by creating an action method
called onLoginButtonClick in our Main controller and simply output
a console message:
...
onLoginButtonClick: function(){
console.log('Log me in!');
}
...

5. Now, in our Main controller's init method we use the control method to attach
this action method to the Login button's click event:
...
init: function(){
console.log('Main Controller Init');
this.control({
'button[action=login]': {
click: this.onLoginButtonClick,
scope: this
}
});
}
...

...
refs: [{
ref: 'usernameField',
selector: 'textfield[name=Username]'
}, {
ref: 'passwordField',
selector: 'textfield[name=Password]'
}]
...

onLoginButtonClick: function(){
console.log('Log me in!');
console.log(this.getUsernameField().getValue());
console.log(this.getPasswordField().getValue());
}


Architecting your applications with the MVC pattern

1. Start by editing index.html and add the files we require for our app:


Enhancement Log











2. We start the application with an instance of the Ext.app.Application class.
This contains our application name, a reference to the controller(s), and the launch
method that runs once everything has loaded. In app.js add:
Ext.application({
name: 'EnhancementLog',
controllers: ['Enhancement'],
launch: function(){
Ext.create('Ext.container.Viewport', {
layout: 'fit',
items: [{
xtype: 'EnhancementGrid'
}]
});
}
});

3. Now that we have our application defined and ready to launch, let's deal with the
controller. Add the following code to Enhancement.js in the controller directory:
Ext.define('EnhancementLog.controller.Enhancement', {
extend: 'Ext.app.Controller',
stores: ['Enhancement'],
models: ['Enhancement'],
views: ['enhancement.EnhancementGrid'],
init: function() {
//initialization code
}
});

4. Then define the view (in our case an enhancement grid) in EnhancementGrid.js:
Ext.define('EnhancementLog.view.enhancement.EnhancementGrid', {
extend: 'Ext.grid.Panel',
alias: 'widget.EnhancementGrid',
title: 'System Enhancements',
store: 'Enhancement',
columns: [{
header: 'Title',
dataIndex: 'title',
flex: 1
}, {
header: 'Enhancement Description',
dataIndex: 'description',
flex: 3
}]
});

5. We now need to create a model and bind it to a store. The model is defined as follows:
Ext.define('EnhancementLog.model.Enhancement', {
extend: 'Ext.data.Model',
fields: ['id', 'title', 'description']
});

6. Finally we define a store (with some pre-defined data) like so:
Ext.define('EnhancementLog.store.Enhancement', {
extend: 'Ext.data.Store',
model: 'EnhancementLog.model.Enhancement',
data: [{
id: 1,
title: 'Search Field Autocomplete',
description: 'Could the main search field have an
autocomplete facility to increase my productivity.'
}]
});


Advanced functionality with plugins

var form = Ext.create('Ext.form.Panel', {
renderTo: Ext.getBody(),
bbar: [{
xtype: 'button',
text: 'Edit'
}, {
xtype: 'button',
text: 'Save'
}, {
xtype: 'button',
text: 'Cancel'
}],
items: [{
xtype: 'textfield',
fieldLabel: 'Email Address'
}]
});

1. Plugins are simply classes, in the same way that all components are, so we start
by defining our Ext.ux.ReadOnlyField class that will, by default, extend the
Ext.Base class:
Ext.define('Ext.ux.ReadOnlyField', {
});

2. The next step is to define our plugin's init method, which is the starting point
of every plugin. To start with, we simply cache a reference to the plugin's parent
component (that is, the text field) so we can easily access it later:
Ext.define('Ext.ux.ReadOnlyField', {
init: function(parent){
this.parent = parent;
}
});

3. We will use the parent component's render event to create our plugin's extra
markup. We attach a handler method that creates a new DIV element inside
the field's body element:
Ext.define('Ext.ux.ReadOnlyField', {
init: function(parent){
this.parent = parent;
this.initEventHandlers();
},
initEventHandlers: function(){
this.parent.on({
render: this.onParentRender,
scope: this
});
},
onParentRender: function(field){
field.displayEl = Ext.DomHelper.append(field.bodyEl, {
tag: 'div',
style: {
height: '22px',
"line-height": '18px',
margin: '2px 0 0 5px'
}
}, true).setVisibilityMode(Ext.Element.DISPLAY);
field.inputEl.setVisibilityMode(Ext.Element.DISPLAY);
}
});

4. We now add three methods, which will switch between read-only and edit modes.
These methods show and hide the appropriate elements and set the values of them
as needed:
...
edit: function(){
if(this.rendered){
this.displayEl.hide();
this.inputEl.show();
this.cachedValue = this.getValue();
}
},
save: function(){
if(this.rendered){
this.displayEl.update(this.getValue());
this.displayEl.show();
this.inputEl.hide();
}
},
cancel: function(){
if(this.rendered){
this.setValue(this.cachedValue);
this.displayEl.show();
this.inputEl.hide();
}
}
...

5. In order for these methods to be called from the field directly we create a reference to
them in the field's class inside the init method:
init: function(parent){
this.parent = parent;
this.initEventHandlers();
this.parent.edit = this.edit;
this.parent.save = this.save;
this.parent.cancel = this.cancel; }

6. We can now add handlers to our three toolbar buttons to call the relevant method:
...
bbar: [{
xtype: 'button',
text: 'Edit',
handler: function(){
form.items.get(0).edit();
}
}, {
xtype: 'button',
text: 'Save',
handler: function(){
form.items.get(0).save();
}
}, {
type: 'button',
text: 'Cancel',
handle: function(){
form.items.get(0).cancel();
}
}]
...
7. Finally, we attach the plugin to our text field by creating a new plugin instance and
including it in the field's plugins array:
{
xtype: 'textfield',
fieldLabel: 'Email Address',
plugins: [Ext.create('Ext.ux.ReadOnlyField')]
}




Creating a live updating chart bound to an editable grid

1. Start by defining a model for the data we are loading into the chart and grid:
Ext.define('LanguageShare', {
extend: 'Ext.data.Model',
fields: [{
name: 'Language',
type: 'string'
}, {
name: 'Percentage',
type: 'int'
}]
});

2. Now define a dataset for the chart and grid:
var languageShareData = [{
Language: 'C',
Percentage: 7
}, {
Language: 'C++',
Percentage: 4
}, {
Language: 'Java',
Percentage: 7
}, {
Language: 'JavaScript',
Percentage: 20
}, ...];

3. Create an Ext.data.Store and load in our dataset. This will be used to bind the
data to the grid and chart:
var languageShareStore = Ext.create('Ext.data.Store', {
model: 'LanguageShare',
data: languageShareData
});

4. Create a basic chart with a pie series. Assign it to the variable chart:
var chart = Ext.create('Ext.chart.Chart', {
height: 400,
width: 400,
store: languageShareStore,
animate: true,
series: [{
type: 'pie',
angleField: 'Percentage',
label: {
display: 'rotate',
contrast: true,
field: 'Language'
}
}]
});

5. Create a grid panel with a CellSelectionModel and CellEditing plugin. Ensure
that the Percentage column is editable by adding a numberfield. Assign the
editor grid to the variable grid:
var grid = Ext.create('Ext.grid.Panel', {
store: languageShareStore,
height: 400,
width: 400,
border: 0,
columns: [{
header: 'Language',
dataIndex: 'Language',
flex: 1
}, {
header: 'Percentage',
dataIndex: 'Percentage',
flex: 1,
field: {
xtype: 'numberfield',
allowBlank: false
}
}],
selType: 'cellmodel',
plugins: [Ext.create('Ext.grid.plugin.CellEditing', {
clicksToEdit: 1
})]
});

6. Finally, create a panel with an hbox layout and render it to the document's body.
Add the grid and chart to the panel's items collection:
var panel = Ext.create('Ext.Panel', {
width: 800,
height: 427,
title: 'GitHub Language Share',
layout: {
type: 'hbox',
align: 'stretch'
},
items: [grid, chart],
style: 'margin: 50px',
renderTo: Ext.getBody()
});



Attaching events to chart components

1. Start by defining a model for the data we are loading into the chart:
Ext.define('Chart', {
extend: 'Ext.data.Model',
fields: [{
name: 'name',
type: 'string'
}, {
name: 'value',
type: 'int'
}]
});

2. Create a store with an AJAX proxy. Set autoLoad: true to load the
data automatically:
var store = Ext.create('Ext.data.Store', {
model: 'Chart',
proxy: {
type: 'ajax',
url: 'BarChart.json',
reader: {
type: 'json',
root: 'data'
}
},
autoLoad: true
});

3. Create a basic chart rendered to the document's body. Give it a Numeric and
Category axis and set a bar series:
var chart = Ext.create('Ext.chart.Chart', {
width: 600,
height: 400,
animate: true,
store: store,
axes: [{
type: 'Numeric',
position: 'bottom',
fields: ['value'],
title: 'Value'
}, {
type: 'Category',
position: 'left',
fields: ['name'],
title: 'Name'
}],
series: [{
type: 'bar',
axis: 'bottom',
xField: 'name',
yField: 'value'
}],
style: 'margin: 50px',
renderTo: Ext.getBody()
});

6. To demonstrate this working in a line chart change the series to the following:
var chart = Ext.create('Ext.chart.Chart', {
...
series: [{
type: 'line',
axis: 'left',
xField: 'name',
yField: 'value',
listeners: {
itemmousedown: function(item){
console.log('Mouse Pressed');
},
itemmouseup: function(item){
console.log('Mouse Up');
},
itemmouseout: function(item){
console.log('Mouse Out');
},
itemmouseover: function(item){
console.log('Mouse Over');
}
}
}],
...
});

Chart customizing labels, colors, and axes

1. Start by defining a model to define the data we are loading:
Ext.define('Chart', {
extend: 'Ext.data.Model',
fields: [{
name: 'name',
type: 'string'
}, {
name: 'value',
type: 'int'
}]
});

2. Create a store with an AJAX proxy:
var store = Ext.create('Ext.data.Store', {
model: 'Chart',
proxy: {
type: 'ajax',
url: 'BarChart.json',
reader: {
type: 'json',
root: 'data'
}
},
autoLoad: true
});

3. Create a chart bound to the store and render it to the document's body:
var chart = Ext.create('Ext.chart.Chart', {
width: 600,
height: 400,
animate: true,
store: store,
style: 'margin: 50px',
renderTo: Ext.getBody()
});

4. Add the theme config to the chart and set it to red, then apply a gradient to
the background:
var chart = Ext.create('Ext.chart.Chart', {
...
theme: 'Red',
background: {
gradient: {
angle: 30,
stops: {
0: {
color: '#FFFFFF'
},
100: {
color: '#FFDBDB'
}
}
}
}
});

5. We need axes for the bar chart. Add a Numeric and Category axis as follows:
var chart = Ext.create('Ext.chart.Chart', {
...
axes: [{
type: 'Numeric',
position: 'bottom',
fields: ['value'],
title: 'Value',
minimum: 1,
maximum: 35,
decimals: 0,
majorTickSteps: 10,
minorTickSteps: 5,
grid: {
'stroke-width': 2
}
}, {
type: 'Category',
position: 'left',
fields: ['name'],
title: 'Name',
label: {
rotate: {
degrees: 315
}
}
}]
});

6. Finally, add a bar series. The label, in this case, adds the values for each bar:
var chart = Ext.create('Ext.chart.Chart', {
...
series: [{
type: 'bar',
axis: 'bottom',
xField: 'name',
yField: 'value',
label: {
field: 'value',
display: 'insideStart',
orientation: 'horizontal',
color: '#333'
}
}]
});

Creating a bespoke theme
Creating a bespoke theme is done by extending the Ext.chart.theme.Base class and
adding your own custom styles/colors to its various properties.
The following theme is by no means complete. However, it illustrates how to go about
creating one:
Ext.define('Ext.chart.theme.MyTheme', {
extend: 'Ext.chart.theme.Base',
constructor: function(config){
this.callParent([Ext.apply({
axis: {
fill: '#ccc',
stroke: '#ccc'
},
colors: ['#111', '#333', '#555', '#777', '#000']
}, config)]);
}
});

Creating a line chart with updating data

1. We start by creating a simple Ext.data.Model to represent our HeartRate
data object:
Ext.define('HeartRate', {
extend: 'Ext.data.Model',
fields: [{
name: 'SecondsElapsed',
type: 'int'
}, {
name: 'BeatsPerMinute',
type: 'int'
}]
});

2. Next we create an Ext.data.Store that we will bind to our chart. We set this up to
point to our HeartRate.php file discussed earlier. We also attach a handler function
to our store's beforeload event where we increment the currentCount variable
and attach it as a parameter to our AJAX calls:
var currentCount = 0;
var maxDisplayCount = 20;
var heartRateStore = Ext.create('Ext.data.Store', {
model: 'HeartRate',
proxy: {
type: 'ajax',
url: 'HeartRate.php',
reader: {
type: 'json',
root: 'data'
}
},
autoLoad: true,
listeners: {
beforeload: function(store, operation, opts){
currentCount++;
operation.params = {
currentCount: currentCount
};
}
}
});

3. Now we add a listener to the store's load event. This listener will be tasked with
updating the chart's x-axis' minimum and maximum values so that they stay in sync
with the data and only show 20 values at a time (defined by our maxDisplayCount
variable). We then redraw the chart:
load: function(store, records){
var chart = panel.items.get(0),
secondsElapsedAxis = chart.axes.get(1),
secondsElapsed = records[0].get('SecondsElapsed');
secondsElapsedAxis.maximum = store.getCount() < maxDisplayCount
? maxDisplayCount : secondsElapsed;
secondsElapsedAxis.minimum = store.getCount() < maxDisplayCount
? 0 : secondsElapsed - maxDisplayCount;
chart.redraw();
}

4. The next step is to create an Ext.Panel with an Ext.chart.Chart instance within
it. The chart should then be bound to heartRateStore:
var panel = Ext.create('Ext.Panel', {
width: 600,
height: 400,
title: 'Line Chart - Heart Rate Monitor',
layout: 'fit',
items: [{
xtype: 'chart',
animate: true,
store: heartRateStore
}],
style: 'margin: 50px',
renderTo: Ext.getBody()
});

5. We are now able to define the chart's series as the 'line' type and set its xField
and yField to be the SecondsElapsed and BeatsPerMinute fields respectively:
...
series: [{
type: 'line',
smooth: false,
axis: 'left',
xField: 'SecondsElapsed',
yField: 'BeatsPerMinute'
}]
...

6. The chart's numeric axes are now declared. The y-axis is bound to the
BeatsPerMinute field and given a position of left. The x-axis is bound
to the SecondsElapsed field and positioned at the bottom:
...
axes: [{
type: 'Numeric',
grid: true,
position: 'left',
field: ['BeatsPerMinute'],
title: 'Beats per Minute',
minimum: 0,
maximum: 200,
majorTickSteps: 5
}, {
type: 'Numeric',
position: 'bottom',
fields: 'SecondsElapsed',
title: 'Seconds Elapsed',
minimum: 0,
maximum: 20,
decimals: 0,
constrain: true,
majorTickSteps: 20
}],
...
7. Finally, we make the magic happen by creating a repeating function using
the setInterval function. We pass this a simple function that calls the
heartRateStore's load method, which is configured to append the newly
loaded records to the existing ones, instead of replacing them:
setInterval(function(){
heartRateStore.load({
addRecords: true
});
}, 1000);




Creating a pie chart with local data

1. As with all of our data-bound examples, we start by creating an Ext.data.Model.
We give it two fields—Language and Percentage:
Ext.define('LanguageShare', {
extend: 'Ext.data.Model',
fields: [{
name: 'Language',
type: 'string'
}, {
name: 'Percentage',
type: 'int'
}]
});

2. Now we define a data set to display in our chart. I have omitted some of the data
for brevity:
var languageShareData = [{
Language: 'C',
Percentage: 7
},
...
{
Language: 'Others',
Percentage: 22
}];

3. Next we create an Ext.data.Store and load in our dataset. This will be used to
bind to our chart:
var languageShareStore = Ext.create('Ext.data.Store', {
model: 'LanguageShare',
data: languageShareData
});

4. Define an Ext.Panel containing an Ext.chart.Chart configuration object, using
its xtype, as its only item:
var panel = Ext.create('Ext.Panel', {
width: 600,
height: 400,
title: 'Pie Chart - Language\'s Share of GitHub Repositories',
layout: 'fit',
items: [{
xtype: 'chart'
}],
style: 'margin: 50px',
renderTo: Ext.getBody()
});
5. We can now bind our chart to the Store we created in Step 3:
...
{
xtype: 'chart',
store: languageShareStore
}
...
6. Our next step is to create our Ext.chart.series.Pie instance that will tell the
chart that we want the data to be represented as a pie chart. We tell it that the
Percentage field is the one to use to calculate the size of each slice:
{
xtype: 'chart',
store: languageShareStore,
series: [{
type: 'pie',
angleField: 'Percentage'
}]
}

7. At this stage our pie chart will render our data but, by default, won't have any labels
attached so we don't know which slice refers to which data! We can add a label to
each slice by using the label property and configuring it with the field to grab the
label value from and some other display properties:
{
type: 'pie',
angleField: 'Percentage',
label: {
display: 'rotate',
contrast: true,
field: 'Language'
}
}


Creating a bar chart with external data

1. Start by defining a model to define the data we are loading:
Ext.define('Chart', {
extend: 'Ext.data.Model',
fields: [{
name: 'name',
type: 'string'
}, {
name: 'value',
type: 'int'
}]
});

2. Create a store with an AJAX proxy:
var store = Ext.create('Ext.data.Store', {
model: 'Chart',
proxy: {
type: 'ajax',
url: 'BarChart.json',
reader: {
type: 'json',
root: 'data'
}
},
autoLoad: true
});

3. Create a panel with a fit layout and render it to the document's body:
var panel = Ext.create('Ext.Panel', {
width: 600,
height: 400,
title: 'Bar Chart from External Data',
layout: 'fit',
items: [
...
],
style: 'margin: 50px',
renderTo: Ext.getBody()
});

4. In the panel's items collection add a basic chart bound to the store created in
step 2. The chart requires numeric and category axes and a bar series:
var panel = Ext.create('Ext.Panel', {
...
items: {
xtype: 'chart',
animate: true,
store: store,
axes: [{
type: 'Numeric',
position: 'bottom',
fields: ['value'],
title: 'Value'
}, {
type: 'Category',
position: 'left',
fields: ['name'],
title: 'Name'
}],
series: [{
type: 'bar',
axis: 'bottom',
xField: 'name',
yField: 'value'
}]
},
...
});


Using the color picker in a menu

1. Start by creating a button and add a new instance of a ColorPicker to the button's
menu property:
var button = Ext.create('Ext.button.Button', {
text: 'Change the Background Color',
menu: new Ext.menu.ColorPicker({
...
})
});

2. Create a Panel with the button in a toolbar that's docked to the top.
var panel = Ext.create('Ext.Panel', {
height: 300,
width: 400,
dockedItems: [{
xtype: 'toolbar',
dock: 'top',
items: [button]
}],
renderTo: Ext.getBody(),
style: 'margin: 50px'
});

3. Add a handler to the ColorPicker to change the body background color to the
selected color:
...
menu: new Ext.menu.ColorPicker({
handler: function(picker, color){
Ext.getBody().setStyle('background-color', '#' + color);
}
})
...

Adding a combobox to a toolbar to filter a grid

3. Next we create an Ext.form.field.ComboBox bound to our new store, with its
displayField and valueField properties set to our store's only field:

var filterCombo = Ext.create('Ext.form.field.ComboBox', {
fieldLabel: 'Status Filter',
labelWidth: 70,
queryMode: 'local',
displayField: 'Status',
valueField: 'Status',
store: statusStore
});

var invoicesGrid = Ext.create('Ext.grid.Panel', {
title: 'Chapter 9',
height: 300,
width: 600,
store: invoiceStore,
dockedItems: [{
xtype: 'toolbar',
dock: 'top',
items: [filterCombo]
}],
columns: [{
header: 'Client',
dataIndex: 'Client'
}, {
header: 'Date',
dataIndex: 'Date'
}, {
header: 'Amount',
dataIndex: 'Amount'
}, {
header: 'Status',
dataIndex: 'Status'
}],
renderTo: Ext.getBody()
});

filterCombo.on('select', function(combo, records, opts) {
invoicesGrid.getStore().clearFilter();
// if there are selected records and the first isn't
// 'All' then apply the filter
if(records.length > 0 && records[0].get('Status') !== 'All') {
var filterStatus = records[0].get('Status');
invoicesGrid.getStore().filter('Status', filterStatus);
}
});

Working with context menus

invoicesGrid.on({
itemcontextmenu: function(grid, record, item, index, e, eOpts)
{
e.stopEvent();
var viewBtn = contextMenu.items.get(0);
var editBtn = contextMenu.items.get(1);
var resendBtn = contextMenu.items.get(2);
var archiveBtn = contextMenu.items.get(3);
var deleteBtn = contextMenu.items.get(4);
var status = record.get('Status');
switch (status) {
case 'Paid':
viewBtn.enable();
editBtn.disable();
resendBtn.disable();
archiveBtn.enable();
deleteBtn.disable();
break;
case 'Sent':
viewBtn.enable();
editBtn.enable();
resendBtn.enable();
archiveBtn.disable();
deleteBtn.enable();
break;
case 'Viewed':
viewBtn.enable();
editBtn.enable();
resendBtn.enable();
archiveBtn.disable();
deleteBtn.enable();
break;
default:
}
contextMenu.showAt(e.getXY());
}
});

Adding buttons to grid rows with action columns

4. We can add multiple icons to an actioncolumn by defining the actions in the
Ext.grid.column.Action's items collection. Each action will have its own
click handler:
Ext.create('Ext.grid.Panel', {
...
columns: [
...
{
xtype: 'actioncolumn',
items: [{
icon: 'icons/pencil.png',
tooltip: 'Edit',
handler: function(grid, rowIndex, colIndex){
alert('Show "Edit Invoice" component');
}
}, {
icon: 'icons/minus-circle.png',
tooltip: 'Delete',
handler: function(grid, rowIndex, colIndex){
Ext.Msg.show({
title: 'Delete Invoice',
msg: 'Confirm deleting this invoice',
buttons: Ext.Msg.YESNO,
icon: Ext.Msg.QUESTION
});
}
}, {
icon: 'icons/money.png',
tooltip: 'Enter Payment',
handler: function(grid, rowIndex, colIndex){
Ext.Msg.prompt('Enter Payment', 'Payment
Amount:');
}
}]
}]
...
});

Ext.create('Ext.grid.Panel', {
...
columns: [{
xtype: 'actioncolumn',
hideable: false,
items: [{
getClass: function(v, meta, rec){
switch (rec.get('Status')) {
case 'Paid':
this.items[3].tooltip = 'This invoice has
been paid.';
return 'paid';
default:
this.items[3].tooltip = 'This invoice has
not yet been paid.';
return 'not-paid';
}
},
handler: function(grid, rowIndex, colIndex){
var rec = grid.getStore().getAt(rowIndex);
alert('Status: ' + rec.get('Status'));
}
}]
}],
...
});

Populating a form from a selected grid row

3. Ensure the grid has a row selection model and add a listener to listen to the
select event of Ext.selection.RowModel. When a row is selected we load
the record into the form panel:
var grid = Ext.create('Ext.grid.Panel', {
...
selType: 'rowmodel',
listeners: {
select: function(RowModel, record, index, eOpts){
formPanel.loadRecord(record);
}
},
...
});

4. Add a button to the form panel to submit the data back to the Model. This will update
the grid and submit the data to the server.
var formPanel = Ext.create('Ext.form.Panel', {
...
buttons: [{
text: 'Update Data',
handler: function(){
var form = this.up('form').getForm();
var recordIndex = form.getRecord().index;
var formValues = form.getValues();
var record = grid.getStore().getAt(recordIndex);
record.beginEdit();
record.set(formValues);
record.endEdit();
}
}],
...
});

Adding a context menu to grid rows

2. We now instantiate an Ext.menu.Menu object and configure it with a height, width,
and a collection of Ext.menu.Items that will be our action buttons:

var contextMenu = Ext.create('Ext.menu.Menu', {
height: 100,
width: 125,
items: [{
text: 'View Invoice',
icon: 'icons/document-text.png'
}, {
text: 'Edit Invoice',
icon: 'icons/pencil.png'
}, {
text: 'Re-Send Invoice',
icon: 'icons/envelope-label.png'
}, {
text: 'Delete Invoice',
icon: 'icons/minus-circle.png'
}]
});

3. We can now hook the right-click event of the grid to a function that will show our
menu. This event is named itemcontextmenu and will fire when a grid row is
right-clicked.

4. We add a handler by using the grid's on method and make two method calls
inside it. The first, stopEvent, prevents the right-click event from showing the
browser's native context menu; and the second displays our menu at the position
the right-click occurred:

invoicesGrid.on({
itemcontextmenu: function(view, record, item, index, e){
e.stopEvent();
contextMenu.showAt(e.getXY());
}
});

Displaying full-width row data with the RowBody feature

2. Our next step is to create a new Ext.grid.feature.RowBody instance and add it
to our grid's features configuration option:
...
features: [Ext.create('Ext.grid.feature.RowBody', {})],
...

3. If we view the result so far, we can see the RowBody being added but it is empty.

4. We must now configure it to display the data we want. We do this by overriding the
getAdditionalData method of the Ext.grid.feature.RowBody class, which
returns an object literal that is applied to the RowBody's markup template. We set
the rowBody property of the returned object to the description contained in our
Model's data.
features: [Ext.create('Ext.grid.feature.RowBody', {
getAdditionalData: function(data){
var headerCt = this.view.headerCt,
colspan = headerCt.getColumnCount();
return {
rowBody: data.Description,
rowBodyCls: this.rowBodyCls,
rowBodyColspan: colspan
};
}

6. We solve this problem by adding another grid feature called the Ext.grid.
feature.RowWrap to the grid's features array. We do this by using its ftype
(which, as mentioned earlier, is similar to an xtype but is specific for features):
...
{
ftype: 'rowwrap'
}
...


Creating summary rows aggregating the grid's data

1. Create a grid panel with the following configuration and column configuration:
Ext.create('Ext.grid.Panel', {
title: 'Summary Example',
height: 300,
width: 600,
store: invoiceStore,
columns: [{
header: 'Client',
dataIndex: 'Client',
flex: 1
}, {
header: 'Date',
dataIndex: 'Date',
xtype: 'datecolumn',
format: 'd/m/Y'
}, {
header: 'Amount',
dataIndex: 'Amount',
xtype: 'numbercolumn'
}, {
header: 'Status',
dataIndex: 'Status'
}],
style: 'margin: 50px',
renderTo: Ext.getBody()
});

2. In the grid's features collection add summary as ftype. Then add a summaryType
and custom summaryRenderer to the Amount column to sum the total value of
all invoices:
Ext.create('Ext.grid.Panel', {
...
features: [{
ftype: 'summary'
}],
columns: [{
header: 'Client',
dataIndex: 'Client',
flex: 1
}, {
header: 'Date',
dataIndex: 'Date',
xtype: 'datecolumn',
format: 'd/m/Y'
}, {
header: 'Amount',
dataIndex: 'Amount',
xtype: 'numbercolumn',
summaryType: 'sum',
summaryRenderer: function(value, summaryData, field){
return '£' + Ext.Number.toFixed(value, 2);
}
}, {
header: 'Status',
dataIndex: 'Status'
}],
...
});

3. Finally, add a count summary to the Client column and apply a custom
summaryRenderer to display the total number of invoices in this grid:
Ext.create('Ext.grid.Panel', {
...
columns: [{
header: 'Client',
dataIndex: 'Client',
flex: 1,
summaryType: 'count',
summaryRenderer: function(value, summaryData, field){
return Ext.String.format('{0} Invoice{1}', value, value !== 1 ? 's' : '');
}
},
...
]
...
});

4. To customize the style of the summary row, add the following CSS in the HEAD
element of your HTML page:



Custom rendering of grid cells with TemplateColumns

1. Now we have the Model and Store in place. We can create a basic grid panel that is
bound to our invoiceStore. This grid has columns defined for four of the Model's
fields: Client, Date, Amount, and Status:
Ext.create('Ext.grid.Panel', {
title: 'Chapter 8 - Grids',
height: 300,
width: 600,
store: invoiceStore,
columns: [{
header: 'Client',
dataIndex: 'Client'
}, {
header: 'Date',
dataIndex: 'Date'
}, {
header: 'Amount',
dataIndex: 'Amount'
}, {
header: 'Status',
dataIndex: 'Status'
}]
});

2. Now we have our basic structure in place we can define the Client column as an
Ext.grid.column.Template column. We do this by adding an xtype to its
column definition and giving it the value templatecolumn:
...
xtype: 'templatecolumn',
...

3. We can add the tpl configuration option and assign it a string containing
our template. We will simply display the client's value and then the invoice's
description in a span under it. We are also going to add a QuickTip to it in case
it is too long for the cell to contain:
{
xtype: 'templatecolumn',
header: 'Client',
dataIndex: 'Client',
tpl: '{Client}
{Description}'
}

4. Finally, we'll add a quick CSS style to make it look a bit better:
.description {
color: #666;
font-size: 0.9em;
margin-top: 4px;
}

1. As before, we start by adding the Ext.grid.column.Template column's xtype
(templatecolumn) to the column.
2. Our next step is to assign the tpl option an Ext.XTemplate object and define the
template string itself:
...
tpl: new Ext.XTemplate(
'{Amount}',
'{Currency}'
)
...
3. If you view this you will see the Currency field showing but our Amount field is not
showing two decimal places. We can fix this by adding a formatting function as part of
our Ext.XTemplate (as we have done in previous chapters) and use this to format
our float:
tpl: new Ext.XTemplate(
'{Amount:this.formatAmount}',
'{Currency}', {
formatAmount: function(val){
return val.toFixed(2);
}
)
4. As in the first example, we will add some CSS styling to format our currency type and
the outcome can be seen in the following screenshot:

Making use of the grouping feature events and methods

Ext.create('Ext.grid.Panel', {
...
listeners: {
groupclick: function(grid, node, group){
alert(group);
}
},
...
});

Dragging-and-dropping records between grids

1. Having included our basic Invoice Store and Model, our first step is to create our two
grids. The first, containing unpaid invoices, will be bound to our invoicesStore,
and the second bound to a new empty store:
var unpaidInvoicesGrid = Ext.create('Ext.grid.Panel', {
title: 'Unpaid Invoices',
height: 300,
width: 600,
store: invoiceStore,
columns: [{
header: 'Client',
dataIndex: 'Client'
}, {
header: 'Date',
dataIndex: 'Date'
}, {
header: 'Amount',
dataIndex: 'Amount'
}, {
header: 'Status',
dataIndex: 'Status'
}],
renderTo: Ext.getBody()
});

var paidInvoicesGrid = Ext.create('Ext.grid.Panel', {
title: 'Unpaid Invoices',
height: 300,
width: 600,
store: new Ext.data.Store({
model: 'Invoice'
}),
columns: [{
header: 'Client',
dataIndex: 'Client'
}, {
header: 'Date',
dataIndex: 'Date'
}, {
header: 'Amount',
dataIndex: 'Amount'
}, {
header: 'Status',
dataIndex: 'Status'
}],
renderTo: Ext.getBody()
});

2. Next, we configure the Ext.grid.plugin.DragDrop plugin as part of each
grid's view configuration. It takes two options, in addition to its ptype value,
namely dragGroup and dropGroup:
// Unpaid Invoices Grid
...
viewConfig: {
plugins: [{
ptype: 'gridviewdragdrop',
dragGroup: 'unpaid-group',
dropGroup: 'paid-group'
}]
}
...
// Paid Invoices Grid
...
viewConfig: {
plugins: [{
ptype: 'gridviewdragdrop',
dragGroup: 'paid-group',
dropGroup: 'unpaid-group'
}]
}
...

Updating a row's data after dropping

1. We start by adding a listener for the drop event to the viewConfig of the unpaid
invoices grid:
...
viewConfig: {
plugins: [{
ptype: 'gridviewdragdrop',
dragGroup: 'unpaid-group',
dropGroup: 'paid-group'
}],
listeners: {
drop: function(node, data, overModel, dropPosition){
}
}
}

...

2. We can now access an array of the models that are being dragged from the data
parameter's records property. We will then iterate through this array and update
each model's Status field to Unpaid:
...
drop: function(node, data, overModel, dropPosition){
var records = data.records;
Ext.each(records, function(record){
record.set('Status', 'Unpaid');
});
}
...

3. We can add the same code to the paid invoices grid; but replacing the Unpaid string
with Paid:
...
drop: function(node, data, overModel, dropPosition){
var records = data.records;
Ext.each(records, function(record){
record.set('Status', 'Unpaid');
});
}
...

Allowing rows to be reordered with drag-and-drop

viewConfig: {
plugins: [{
ptype: 'gridviewdragdrop',
dragGroup: 'unpaid-group',
dropGroup: 'paid-group',
}, {
ptype: 'gridviewdragdrop',
dragGroup: 'unpaid-group',
dropGroup: 'unpaid-group',
}],
listeners: {
drop: function(node, data, overModel, dropPosition){
var records = data.records;
Ext.each(records, function(record){
record.set('Status', 'Unpaid');
});
}
}
}

Adding a paging toolbar for large datasets

1. Define a Store with an AjaxProxy for binding to the grid. Set the store's pageSize
configuration option to 50. Assign the store to the variable invoiceStore:
var invoiceStore = Ext.create('Ext.data.Store', {
autoLoad: true,
model: 'Invoice',
pageSize: 50,
proxy: {
type: 'ajax',
url: 'invoices.php',
reader: {
type: 'json',
root: 'rows'
}
}
});

2. Create a grid that is bound to the invoiceStore (created in step 1) with the
following column configuration:
Ext.create('Ext.grid.Panel', {
title: 'Paging Toolbar',
height: 300,
width: 600,
store: invoiceStore,
columns: [{
header: 'Client',
dataIndex: 'Client',
flex: 1
}, {
header: 'Date',
dataIndex: 'Date'
}, {
header: 'Amount',
dataIndex: 'Amount'
}, {
header: 'Status',
dataIndex: 'Status'
}],
renderTo: Ext.getBody()
});

3. In the grid's configuration add an Ext.PagingToolbar docked to the bottom of the
grid through the bbar config option. The paging toolbar should also be bound to the
same Store as the grid (invoiceStore):
Ext.create('Ext.grid.Panel', {
...
bbar: Ext.create('Ext.PagingToolbar', {
store: invoiceStore,
displayInfo: true,
displayMsg: 'Displaying Invoices {0} - {1} of {2}',
emptyMsg: "No invoices to display"
})
...
});


Editing grid data with a RowEditor

1. We need to ensure that the store is ready for submitting saved data to the server. In
this case we are going to define a JsonWriter on the store. Set the writer to only
submit changed fields to the server:
var invoiceStore = Ext.create('Ext.data.Store', {
...
proxy: {
...
writer: {
type: 'json',
writeAllFields: false
}
}
});

2. The next step is to create the RowEditing plugin. It's possible to add custom
configuration to the plugin. For example, set clicksToEdit: 1 so that the
RowEditor will appear after a click to a cell:
var rowEditing = Ext.create('Ext.grid.plugin.RowEditing', {
clicksToEdit: 1
});

3. Create a grid panel that is bound to the invoiceStore.

4. Add the rowEditing plugin and set the Grid's selection model to rowmodel.
Ext.create('Ext.grid.Panel', {
title: 'Row Editing Example',
height: 300,
width: 600,
store: invoiceStore,
plugins: [rowEditing],
selType: 'rowmodel',
columns: [],
style: 'margin: 50px',
renderTo: Ext.getBody()
});

5. Finally, define the columns for the grid. We also add an editor configuration to each
of the columns that require editing capabilities. These editors are the fields rendered
when in edit mode:
Ext.create('Ext.grid.Panel', {
...
columns: [{
header: 'Client',
dataIndex: 'Client',
flex: 1,
editor: {
allowBlank: false
}
}, {
header: 'Date',
dataIndex: 'Date',
format: 'd/m/Y',
xtype: 'datecolumn',
editor: {
xtype: 'datefield',
format: 'd/m/Y',
allowBlank: false
}
}, {
header: 'Amount',
dataIndex: 'Amount',
xtype: 'numbercolumn',
editor: {
xtype: 'numberfield',
allowBlank: false,
hideTrigger: true,
minValue: 1,
maxValue: 150000
}
}, {
header: 'Status',
dataIndex: 'Status'
}],
...
});

Ext.create('Ext.grid.Panel', {
...
plugins: [cellEditing],
selType: 'cellmodel',
columns: [{
header: 'Client',
dataIndex: 'Client',
flex: 1,
editor: {
allowBlank: false
}
}, {
header: 'Date',
dataIndex: 'Date',
format: 'd/m/Y',
xtype: 'datecolumn',
editor: {
xtype: 'datefield',
format: 'd/m/Y',
allowBlank: false
}
}, {
header: 'Amount',
dataIndex: 'Amount',
xtype: 'numbercolumn',
editor: {
xtype: 'numberfield',
allowBlank: false,
hideTrigger: true,
minValue: 1,
maxValue: 150000
}
}, {
header: 'Status',
dataIndex: 'Status'
}],
...
});

Saving and loading data with HTML5 Local Storage

1. Start by defining the UserSetting model with the following fields. We are going to
assign userID as the idProperty and you will see why this is important later.
Ext.define('UserSetting', {
extend: 'Ext.data.Model',
idProperty: 'userID',
fields: [{
name: 'userID',
type: 'int'
}, {
name: 'fontSize',
type: 'string'
}, {
name: 'theme',
type: 'string'
}, {
name: 'language',
type: 'string'
}, {
name: 'dateFormat',
type: 'string'
}]
});

2. Add a proxy to the UserSetting model, setting the proxy type to localstorage.
We need to give the proxy a unique key prefix for storing items in localStorage. To do
this set id: 'user-settings'.
Ext.define('UserSetting', {
...
proxy: {
type: 'localstorage',
id: 'user-settings'
}
});

3. Now create an instance of the UserSetting model and assign it to the
settings variable.
var settings = Ext.create('UserSetting', {
userID: 1,
fontSize: 'medium',
theme: 'default',
language: 'en-gb',
dateFormat: 'd/m/Y'
});

4. Call the save method on the model instance to persist the data to localStorage.
settings.save();

5. Having saved data to localStorage it is now time to retrieve it. Load the data by
calling the load method on the UserSetting class. Pass in the userID to the first
parameter and a load configuration object to the second parameter. Add a callback
to the load configuration object to prove that we are able to retrieve the data.
UserSetting.load(1, {
callback: function(model, operation){
console.log(model.get('language'));
}
});


Handling Store exceptions

1. Define the Model that we will attempt to load data into:
Ext.define('Book', {
extend: 'Ext.data.Model',
fields: [{
name: 'Title',
type: 'string'
}]
});

2. Add an AJAX Proxy to the Model, defining the url config option as
error-response.json:
Ext.define('Book', {
...
proxy: {
type: 'ajax',
url: 'error-response.json'
}
});

3. Listen for the exception event on the AJAX proxy. The exception event will be fired
should the server return an exception:
proxy: {
...
listeners: {
'exception': function(proxy, response, operation, eOpts){
}
}
}

4. Add logic to the function to change the behavior depending on the type of error.
'exception': function(proxy, response, operation, eOpts){
if (response.status !== 200) {
alert(response.status + ' ' + response.statusText);
} else {
var responseText = Ext.decode(response.responseText);
alert(responseText.error);
}
}

Grouping a Store's data

1. We will start by examining the XML output of the Twitter API and identifying which
fields we want and how it is structured. A sample of the data can be seen as follows
(some data has been omitted to save space):


Published Date

</p> <p> Tweet Contents</p> <p>


Username




2. Our first step is to define a Model that will contain our Twitter feed data. We will only
map the useful fields that we included, which contain the user, the tweet itself, and
the published date:
Ext.define('Tweet', {
extend: 'Ext.data.Model',
fields: [{
name: 'user',
mapping: 'author/name',
type: 'string'
}, {
name: 'tweet',
mapping: 'title',
type: 'string'
}, {
name: 'published',
type: 'date'
}]
});

3. We now create an Ext.data.Store that will be made up of Tweet models and have
it load the Tweets with an AJAX proxy, pointing to our static twitterData.xml file:
var twitterStore = Ext.create('Ext.data.Store', {
model: 'Tweet',
proxy: {
type: 'ajax',
url: 'twitterData.xml',
reader: {
type: 'xml',
record: 'entry'
}
}
});
twitterStore.load();

4. Now we can define how we would like to group the store's data. We will group it on the
user field and, after it has loaded, we will log the grouped data to the console:
var twitterStore = Ext.create('Ext.data.Store', {
model: 'Tweet',
proxy: {
type: 'ajax',
url: 'data.xml',
reader: {
type: 'xml',
record: 'entry'
}
},
groupers: [{
property: 'user'
}]
});
twitterStore.load({
callback: function(){
console.log(twitterStore.getGroups());
}
});

5. Finally, we will demonstrate how to group the Store at runtime using the group
method. We will remove our groupers configuration and add a grouping on the
published field:
var twitterStore = Ext.create('Ext.data.Store', {
model: 'Tweet',
proxy: {
type: 'ajax',
url: 'twitterData.xml',
reader: {
type: 'xml',
record: 'entry'
}
}
});
twitterStore.load({
callback: function(){
twitterStore.group('published');
console.log(twitterStore.getGroups());
}
});


Applying validation rules to Models' fields

1. We will start this recipe with the Book Model class we defined in the previous recipes:
Ext.define('Book', {
extend: 'Ext.data.Model',
fields: [{
name: 'Title',
type: 'string'
}, {
name: 'Publisher',
type: 'string'
}, {
name: 'ISBN',
type: 'string'
}, {
name: 'PublishDate',
type: 'date',
dateFormat: 'd-m-Y'
}, {
name: 'NumberOfPages',
type: 'int'
}, {
name: 'Read',
type: 'boolean'
}]
});

2. Now we use the validations configuration to define a minimum length of 1 on the
book's title and make the Publisher field mandatory:
Ext.define('Book', {
extend: 'Ext.data.Model',
fields: [{
name: 'Title',
type: 'string'
}, {
name: 'Publisher',
type: 'string'
}, {
name: 'ISBN',
type: 'string'
}, {
name: 'PublishDate',
type: 'date',
dateFormat: 'd-m-Y'
}, {
name: 'NumberOfPages',
type: 'int'
}, {
name: 'Read',
type: 'boolean'
}],
validations: [{
type: 'length',
field: 'Title',
min: 1
}, {
type: 'presence',
field: 'Publisher'
}]
});

Other built-in validators
There are a total of six built-in validators that can be applied to a Model's fields. In our
previous example, we encountered two of them—presence and length. The other four are
outlined as follows:
ff email: validates that the field contains a valid e-mail address
ff exclusion: accepts a list configuration containing an array of values and will
return true if the field's value is not in the list
ff inclusion: identical to exclusion but evaluates to true if the field's value is present
in the list array
ff format: accepts a matcher configuration option that should contain a regex for the
field's value to be matched against

Creating a custom validator
Ext.data.validations.isbnMessage = 'is not a valid ISBN Number';
Ext.data.validations.isbn = function(config, value){
return true;
};
Ext.data.validations.isbn = function(config, value){
var valid = false;
valid = value.length === 17; // 13 digits + 4 hyphens
valid = valid && (value.split('-').length === 5); // contains 4 hyphens
return valid;
};

Associating Models and loading nested data

1. The first step in linking two Models together is to define them. Start by defining a
Book model:
Ext.define('Book', {
extend: 'Ext.data.Model',
fields: [{
name: 'Title',
type: 'string'
}, {
name: 'Publisher',
type: 'string'
}, {
name: 'ISBN',
type: 'string'
}, {
name: 'PublishDate',
type: 'date',
dateFormat: 'd-m-Y'
}, {
name: 'NumberOfPages',
type: 'int'
}, {
name: 'Read',
type: 'boolean'
}]
});

2. The second Model, Author, should be defined next:
Ext.define('Author', {
extend: 'Ext.data.Model',
fields: [{
name: 'Title',
type: 'string'
}, {
name: 'FirstName',
type: 'string'
}, {
name: 'LastName',
type: 'string'
}, {
name: 'book_id',
type: 'int'
}]
});

3. Add an association to the Book Model:
Ext.define('Book', {
...
associations: [{
type: 'hasMany',
model: 'Author',
name: 'authors'
}]
});

4. Now that we have defined the Book Model, we can create an instance of it containing
some data about this book:
var book = Ext.create('Book', {
Title: 'Ext JS 4 CookBook',
Publisher: 'Packt Publishing',
ISBN: '978-1-849516-86-0',
PublishDate: '01-01-2012',
NumberOfPages: 300,
Read: false
});

5. Run the book.authors() function, which returns a Store for the authors:
var authors = book.authors();

6. Add two authors to the Author Store. These authors will be linked to the book
through a foreign key book_id:
authors.add({
Title: 'Mr',
FirstName: 'Andrew',
LastName: 'Duncan'
}, {
Title: 'Mr',
FirstName: 'Stuart',
LastName: 'Ashworth'
});

7. Create a Store with a Book Model and load the provided books.json file:
var store = Ext.create('Ext.data.Store', {
model: 'Book',
autoLoad: true,
proxy: {
type: 'ajax',
url: 'books.json'
}
});

8. When the load event has been fired we will do some processing to ensure that the
data has been loaded into its respective Models:
store.on('load', function(){
var record = store.getAt(0);
console.log(record);
console.log(record.get('Title'));
var authors = record.getAssociatedData();
console.log(authors);
var author = record.authors().getAt(0);
console.log(author.get('FirstName'));
});



Loading cross-domain data with a Store

1. Start by defining a model to define the data we are loading:
Ext.define('Flickr', {
extend: 'Ext.data.Model',
fields: [{
name: 'title',
type: 'string'
}, {
name: 'link',
type: 'string'
}]
});

2. Create a store with a JSONP proxy:
var JSONPStore = Ext.create('Ext.data.Store', {
model: 'Flickr',
proxy: {
type: 'jsonp',
url:
'http://api.flickr.com/services/feeds/photos_public.gne',
callbackKey: 'jsoncallback',
extraParams: {
tags: 'swan',
tagmode: 'any',
format: 'json'
}
},
reader: {
type: 'json',
root: 'items'
}
});

3. Load data into the Store by calling the store's load method:
JSONPStore.load();

4. Finally, once the load has finished check to make sure that the data has loaded
correctly by returning the first record from the Model:
JSONPStore.on('load', function(){
var record = JSONPStore.getAt(0);
console.log(record.data.title + ' ' + record.data.link);
}, this);

Loading and saving a Model using proxies

Ext.define('Book', {
extend: 'Ext.data.Model',
idProperty: 'BookID',
fields: [{
name: 'BookID',
type: 'int'
}, {
name: 'Title',
type: 'string'
}, {
name: 'Publisher',
type: 'string'
}, {
name: 'ISBN',
type: 'string'
}, {
name: 'PublishDate',
type: 'date',
dateFormat: 'd-m-Y'
}, {
name: 'NumberOfPages',
type: 'int'
}, {
name: 'Read',
type: 'boolean'
}],
validations: [{
type: 'length',
field: 'Title',
min: 1
}, {
type: 'presence',
field: 'Publisher'
}]
});

2. Our next task is to define the Model's proxy. This will define how the Model will load
or save itself when asked to. We will use a simple AJAX proxy with a URL defined for
each of the four CRUD (Create, Read, Update, Delete) actions:
...
proxy: {
type: 'ajax',
api: {
read: 'bookRead.php',
create: 'bookCreate.php',
update: 'bookUpdate.php',
destroy: 'bookDestroy.php'
}
}

3. Now that we have a Proxy set up we can use the Book's static load method to call
the server and fetch a Book's data based on the ID passed in, as in our first example.
As the call is asynchronous we use a callback function to simply log the loaded model
instance once the AJAX call is complete:
Book.load(1, {
callback: function(book, operation){
console.log(book);
}
});

4. If we manually create a new Book model instance, and include a BookID in its data,
we can call the save method and see the bookUpdate.php file being called, with
the Book's data being posted to it:
var book = Ext.create('Book', {
BookID: 1,
Title: 'Ext JS 4 CookBook',
Publisher: 'Packt Publishing',
ISBN: '978-1-849516-86-0',
PublishDate: '01-01-2012',
NumberOfPages: 300,
Read: false
});
book.save();

5. Similarly, if we create a Book without a BookID and call the save method, the
bookCreate.php file with be called with the Book's data passed to it.
var book = Ext.create('Book', {
Title: 'Ext JS 4 CookBook',
Publisher: 'Packt Publishing',
ISBN: '978-1-849516-86-0',
PublishDate: '01-01-2012',
NumberOfPages: 300,
Read: false
});
book.save();

6. Finally, we can delete a Book record by calling the destroy method of the Book
instance, which will cause an AJAX call to be made to the configured destroy URL:
var book = Ext.create('Book', {
BookID: 1,
Title: 'Ext JS 4 CookBook',
Publisher: 'Packt Publishing',
ISBN: '978-1-849516-86-0',
PublishDate: '01-01-2012',
NumberOfPages: 300,
Read: false
});
book.destroy();


Combining form fields

Ext.apply(Ext.form.field.VTypes, {
SortCode: function(val){
var sortCodeRegex = /^(([0-9][0-9])|(99))$/;
return sortCodeRegex.test(val);
},
SortCodeText: 'Must be a numeric value between 00 and 99',
SortCodeMask: /[\d]/i
});

1. Create a form panel:
var formPanel = Ext.create('Ext.form.Panel', {
title: 'Combining Form Fields',
width: 350,
autoHeight: true,
bodyPadding: 10,
defaults: {
anchor: '100%',
labelWidth: 100
},
items: [],
renderTo: Ext.getBody()
});

2. In the panel's items collection add a FieldContainer with an hbox layout:
var formPanel = Ext.create('Ext.form.Panel', {
...
items: [{
xtype: 'fieldcontainer',
fieldLabel: 'Sort Code',
combineErrors: true,
layout: 'hbox',
defaults: {
hideLabel: true,
vtype: 'SortCode'
},
items: []
}],
...
});

3. In the items collection of FieldContainer, add the fields for gathering the
Sort Code:
var formPanel = Ext.create('Ext.form.Panel', {
...
items: [{
...
items: [{
xtype: 'textfield',
name: 'sortcode1',
allowBlank: false,
flex: 1
}, {
xtype: 'displayfield',
value: '-',
margin: '0 0 0 3',
width: 10
}, {
xtype: 'textfield',
name: 'sortcode2',
allowBlank: false,
flex: 1
}, {
xtype: 'displayfield',
value: '-',
margin: '0 0 0 3',
width: 10
}, {
xtype: 'textfield',
name: 'sortcode3',
allowBlank: false,
flex: 1
}]
}],
...
});

4. Finally, add a second field to the panel's items collection for gathering the
account number:
var formPanel = Ext.create('Ext.form.Panel', {
...
items: [{
...
}, {
xtype: 'numberfield',
name: 'accountNumber',
fieldLabel: 'Account Number',
msgTarget: 'side',
minValue: 10000000,
maxValue: 99999999,
hideTrigger: true,
keyNavEnabled: false,
mouseWheelEnabled: false,
allowBlank: false
}],
...
});



Creating repeatable form fields and fieldsets

1. Start by creating a form panel and rendering it to the document's body:
var formPanel = Ext.create('Ext.form.Panel', {
title: 'Reservation Form',
width: 350,
autoHeight: true,
bodyPadding: 10,
defaults: {
labelWidth: 150
},
items: [],
renderTo: Ext.getBody()
});

2. Add some fields to the form's items collection to capture name and Ticket Type
for the first person:
var formPanel = Ext.create('Ext.form.Panel', {
...
items: [{
xtype: 'textfield',
fieldLabel: 'Your Name',
name: 'name'
}, {
xtype: 'radiogroup',
fieldLabel: 'Ticket Type',
items: [{
boxLabel: 'Adult',
name: 'type',
inputValue: 'adult'
}, {
boxLabel: 'Child',
name: 'type',
inputValue: 'child'
}]
}],
...
});

3. Create our GuestFieldSet by extending the Ext.form.FieldSet class:
Ext.define('GuestFieldSet', {
extend: 'Ext.form.FieldSet',
alias: 'widget.GuestFieldSet',
initComponent: function(){
Ext.apply(this, {
title: 'Guest ' + this.guestCount,
collapible: true,
defaultType: 'textfield',
defaults: {
anchor: '100%'
},
layout: 'anchor',
items: [{
fieldLabel: 'Guest ' + this.guestCount + ' Name',
name: 'name-' + this.guestCount
}, {
xtype: 'radiogroup',
fieldLabel: 'Ticket Type',
items: [{
boxLabel: 'Adult',
name: 'type-' + this.guestCount,
inputValue: 'adult'
}, {
boxLabel: 'Child',
name: 'type-' + this.guestCount,
inputValue: 'child'
}]
}]
});
this.callParent(arguments);
}
});

4. Finally, add a button under the fields in the form panel to allow the user to add
a second guest. The button's handler function contains the logic to add the
additional fields:
var formPanel = Ext.create('Ext.form.Panel', {
...
items: [{
...
}, {
xtype: 'button',
text: 'Add Another Guest',
margin: '0 0 5 0',
handler: function(){
guestCount = formPanel.items.length - 2;
formPanel.add({
xtype: 'GuestFieldSet',
guestCount: guestCount
});
}
}],
...
});


Rich editing with an HTML field

1. Initialize the QuickTipManager:
Ext.tip.QuickTipManager.init();

2. Create a narrow form panel and add an HtmlEditor field to the panel's
items collection:
var formPanelNarrow = Ext.create('Ext.form.Panel', {
title: 'HTML Editor (narrow)',
width: 350,
height: 200,
layout: 'fit',
items: [{
xtype: 'htmleditor'
}],
renderTo: Ext.getBody()
});

3. Create a second form panel with the HtmlEditor but this time set the width
to 600:
var formPanelWide = Ext.create('Ext.form.Panel', {
title: 'HTML Editor (wide)',
width: 600,
height: 200,
layout: 'fit',
items: [{
xtype: 'htmleditor'
}],
renderTo: Ext.getBody()
});
4. Create a third form panel with an HTML editor but customize the toolbar options
as follows:
var formPanelCustomOptions = Ext.create('Ext.form.Panel', {
title: 'HTML Editor (customising the toolbar)',
width: 600,
height: 200,
layout: 'fit',
items: [{
xtype: 'htmleditor',
enableSourceEdit: false,
enableColors: false,
enableLinks: false,
fontFamilies: ["Arial", "Tahoma", "Verdana"]
}],
renderTo: Ext.getBody()
});


Rendering the results in a combobox

1. Define an Issue Model:
Ext.define('Issue', {
extend: 'Ext.data.Model',
fields: ['id', 'raisedBy', 'title', 'body', 'status']
});

2. Create a store and add some data for local loading:
var store = Ext.create('Ext.data.Store', {
model: 'Issue',
data: [{
id: 1,
raisedBy: 'Joe',
title: 'Registration Form Not Emailing User',
body: 'The registration email is not being sent to users
upon regisration.',
status: 'Open'
}, {
id: 2,
raisedBy: 'John',
title: 'Account Details Loading Issue',
body: 'The account details page is not loading data from
the server.',
status: 'Closed'
}, {
id: 3,
raisedBy: 'Fred',
title: 'Account Details Missing Email Field',
body: 'The account details page is missing a field to
allow the user to update their email address.',
status: 'Open'
}]
});

3. Add the combobox to a form panel and customize the combo's list through Ext.
view.BoundList (accessible through the listConfig config option):
var formPanel = Ext.create('Ext.form.Panel', {
title: 'Custom ComboBox Results',
width: 500,
autoHeight: true,
bodyPadding: 10,
items: [{
xtype: 'combobox',
fieldLabel: 'Select Issue',
displayField: 'title',
store: store,
queryMode: 'local',
anchor: '100%',
listConfig: {
getInnerTpl: function(){
return '

{title} ({status})

' +
'
Reported by

{raisedBy}
' +'{body}';
}
}
}],
renderTo: Ext.getBody(),
style: 'margin: 50px'
});

Loading server side data into a combobox

{
"success": true,
"users": [{
"fullName": "Joe Bloggs",
"userID": 1
}, {
"fullName": "John Smith",
"userID": 2
}]
}

1. Start by defining a model.
Ext.define('User', {
extend: 'Ext.data.Model',
fields: [{
type: 'string',
name: 'fullName'
}, {
type: 'int',
name: 'userID'
}]
});

2. Create an Ext.data.Store that is linked to the User model:
var store = Ext.create('Ext.data.Store', {
model: 'User',
proxy: {
type: 'ajax',
url: 'users.json',
reader: {
type: 'json',
root: 'users'
}
}
});

3. Create a form panel with a ComboBox and bind it to the store we created above:
var formPanel = Ext.create('Ext.form.Panel', {
title: 'ComboBox with server side data example',
width: 350,
autoHeight: true,
bodyPadding: 10,
items: [{
xtype: 'combobox',
fieldLabel: 'Select User',
displayField: 'fullName',
valueField: 'userID',
store: store,
queryMode: 'remote', //default behavior
forceSelection: true,
anchor: '100%'
}],
renderTo: Ext.getBody(),
style: 'margin: 50px'
});

1. Our first step is to create an Ext.data.Store, which we will bind to our combobox
to give it its list of values. Our example will display a list of car manufacturers:
var carManufacturersStore = Ext.create('Ext.data.Store', {
fields: ['name'],
data: [{
name: 'Aston Martin'
}, {
name: 'Bentley'
}, {
name: 'Daimler'
}, {
name: 'Jaguar'
}, {
name: 'Lagonda'
}, {
name: 'Land Rover'
}, {
name: 'Lotus'
}, {
name: 'McLaren'
}, {
name: 'Morgan'
}, {
name: 'Rolls-Royce'
}]
});

2. We then create a very simple combobox bound to this store and rendered to the
document's body:
Ext.create('Ext.form.ComboBox', {
fieldLabel: 'Car Manufacturer',
store: carManufacturersStore,
queryMode: 'local',
displayField: 'name',
valueField: 'name',
renderTo: Ext.getBody()
});

3. Now, we configure the combobox to find the nearest match after the user has started
typing, and set the value of the combobox's text field. We do this by adding the
typeAhead configuration option:
Ext.create('Ext.form.ComboBox', {
fieldLabel: 'Car Manufacturer',
store: carManufacturersStore,
queryMode: 'local',
displayField: 'name',
valueField: 'name',
typeAhead: true,
renderTo: Ext.getBody()
});

Increasing autocomplete response time

Ext.create('Ext.form.ComboBox', {
fieldLabel: 'Car Manufacturer',
store: carManufacturers,
queryMode: 'local',
displayField: 'name',
valueField: 'name',
typeAhead: true,
typeAheadDelay: 100,
renderTo: Ext.getBody()

});

Removing the combobox's trigger button
Ext.create('Ext.form.ComboBox', {
fieldLabel: 'Car Manufacturer',
store: carManufacturers,
queryMode: 'local',
displayField: 'name',
valueField: 'name',
typeAhead: true,
typeAheadDelay: 100,
hideTrigger: true,
renderTo: Ext.getBody()
});