How to add footer in Grid in sencha - javascript

This is my view part as PurchaseReport.js
Ext.define("App.view.tabs.PurchaseReport", {
extend: "Ext.panel.Panel",
alias: "widget.PurchaseReportTab",
requires: [
"App.model.PurchaseReport", "Ext.toolbar.Toolbar"
],
border: false,
layout: "fit",
items: [
App.Util.buildBrowseConfig({}, {
controller: "PurchaseReport",
primaryKeyField: "PurchaseReportId",
stateful: true,
stateId: "App.view.windows.PurchaseReport-grid",
columns: [
{ dataIndex: "PurchaseCost", filter: true, header: "Purchase Cost", width: 150 }
],
features: [{ ftype: "filters", autoReload: false, local: true }],
store: { model: "App.model.PurchaseReport", sorters: [{ property: "Name", direction: "asc" }] }
})
]
});
This is my controller part where my grid get bind as PurchaseReport.js,
So do I need to make changes over here?
Ext.define("App.controller.tabs.PurchaseReport", {
extend: "Ext.ux.app.BrowseController",
views: ["tabs.PurchaseReport"],
refs: [
{
ref: "myPurchaseReportGrid",
selector: "PurchaseReportTab > gridpanel"
}
],
init: function () {
this.control({
PurchaseReportTab: {
bind: function (a, c) {
**//Grid bind start**
var b = this.getMyPurchaseReportGrid();
b.getSelectionModel().deselectAll();
b.store.removeAll();
b.fireEvent("bind", b, c)
**//Grid bind End**
**//Combobox Bind start**
var combo = this.getCoachCombo(),
store = combo.store,
options = store.lastOptions || {};
options = Ext.apply({
callback: function () {
combo.select(App.coach.CoachId)
//console.log("called rajesh");
}
}, options);
store.load(options);
if (App.coach.IsAdmin) {
combo.show()
}
**//Combobox Bind end**
var abilities = App.coach.Abilities,
toolbar = this.getToolbar();
for (var x = 0; x < abilities.length; x++) {
var ability = abilities[x],
button = toolbar.query("button[tooltip=" + ability.Name + "]");
if (button.length) {
button[0].setVisible(true)
}
}
}
},
"PurchaseReportTab > gridpanel": {
bind: this.bind,
itemdblclick: this.handleRecord,
selectionchange: this.selectionChange
}
})
}
});
This is my model part name as PurchaseReport.js
Ext.define("App.model.PurchaseReport", {
extend: "Ext.data.Model",
fields: [
{
name: "PurchaseDate",
type: "date"
}
],
proxy: {
type: "ajax",
url: "ControllerFactory.aspx",
extraParams: {
controller: "PurchaseReport",
operation: "GetPurchaseReportsByCoachIdAndDates"
},
reader: {
type: "json",
root: "data",
successProperty: "success"
}
}
});
However I am able to get the record display in grid view but I need a footer area where I can display Total amount of Purchase cost. in footer of grid
Please do not find mistake of code as I had deleted many stuff to make it look simpler, as i am new in this please make me know which more details are being needed.
Just a Image for more detail
I have tried adding code as but it is displaying footer in all pages where the Gridview is displaying record, I want to display footer in one page only and not on other pages and also need total of purchase cost displaying on that
Ext.define("Ext.grid.Panel", {
extend: "Ext.panel.Table",
requires: ["Ext.grid.View"],
alias: ["widget.gridpanel", "widget.grid"],
alternateClassName: ["Ext.list.ListView", "Ext.ListView", "Ext.grid.GridPanel"],
viewType: "gridview",
lockable: false,
bothCfgCopy: ["invalidateScrollerOnRefresh", "hideHeaders", "enableColumnHide", "enableColumnMove", "enableColumnResize", "sortableColumns"],
normalCfgCopy: ["verticalScroller", "verticalScrollDock", "verticalScrollerType", "scroll"],
lockedCfgCopy: [],
rowLines: true,
//Newly addded start
height: 200,
width: 400,
bbar: [
{
xtype: 'textfield',
name: 'Total',
fieldLabel: 'Total',
allowBlank: false
}
],
renderTo: Ext.getBody()
//Newly addded end
});

For placing footer you need to use bbar in your code. In your code where you define grid and its config there you need to write
sample example :
bbar: [
{
xtype: 'textfield',
name: 'Total',
fieldLabel: 'Total',
allowBlank: false
}
],
Please read Documentation for better understanding.
Also I make a fiddle for you so you can correlate your project and grid. Fiddle

To add Footer of gridview for some particular page you need to right code in view as (Review comment : //In order to add footer down to gridview for particular pages start)
Ext.define("App.view.tabs.PurchaseReport", {
extend: "Ext.panel.Panel",
alias: "widget.PurchaseReportTab",
requires: [
"App.model.PurchaseReport", "Ext.toolbar.Toolbar"],
border: false,
layout: "fit",
items: [
App.Util.buildBrowseConfig({}, {
controller: "PurchaseReport",
primaryKeyField: "PurchaseReportId",
stateful: true,
stateId: "App.view.windows.PurchaseReport-grid",
columns: [
{ dataIndex: "PurchaseCost", filter: true, header: "Purchase Cost", width: 150 }
],
features: [{ ftype: "filters", autoReload: false, local: true }],
store: { model: "App.model.PurchaseReport", sorters: [{ property: "Name", direction: "asc" }],
//In order to add footer down to gridview for particular pages start
height: 200,
width: 400,
bbar: [
{
dataIndex:"PurchaseCost",
xtype: 'textfield',
name: 'Total',
fieldLabel: 'Total',
allowBlank: false
}
],
renderTo: Ext.getBody()
//In order to add footer down to gridview for particular pages end
}
})
]
});

Use below code
Ext.define('Ext.grid.feature.Summary', {
/* Begin Definitions */
extend: 'Ext.grid.feature.AbstractSummary',
alias: 'feature.summary',
/* End Definitions */
/**
* Gets any fragments needed for the template.
* #private
* #return {Object} The fragments
*/
getFragmentTpl: function() {
// this gets called before render, so we'll setup the data here.
this.summaryData = this.generateSummaryData();
return this.getSummaryFragments();
},
/**
* Overrides the closeRows method on the template so we can include our own custom
* footer.
* #private
* #return {Object} The custom fragments
*/
getTableFragments: function(){
if (this.showSummaryRow) {
return {
closeRows: this.closeRows
};
}
},
/**
* Provide our own custom footer for the grid.
* #private
* #return {String} The custom footer
*/
closeRows: function() {
return '</tpl>{[this.printSummaryRow()]}';
},
/**
* Gets the data for printing a template row
* #private
* #param {Number} index The index in the template
* #return {Array} The template values
*/
getPrintData: function(index){
var me = this,
columns = me.view.headerCt.getColumnsForTpl(),
i = 0,
length = columns.length,
data = [],
active = me.summaryData,
column;
for (; i < length; ++i) {
column = columns[i];
column.gridSummaryValue = this.getColumnValue(column, active);
data.push(column);
}
return data;
},
/**
* Generates all of the summary data to be used when processing the template
* #private
* #return {Object} The summary data
*/
generateSummaryData: function(){
var me = this,
data = {},
store = me.view.store,
columns = me.view.headerCt.getColumnsForTpl(),
i = 0,
length = columns.length,
fieldData,
key,
comp;
for (i = 0, length = columns.length; i < length; ++i) {
comp = Ext.getCmp(columns[i].id);
data[comp.dataIndex] = me.getSummary(store, comp.summaryType, comp.dataIndex, false);
}
return data;
}
});

Related

Problems loading the storage in the renderer for the combobox field

My field looks like this:
...
{
xtype: 'gridcolumn',
text: 'MyField',
dataIndex: 'contragent',
editor: {
xtype: 'combobox',
allowBlank: false,
displayField:'name',
valueField:'id',
queryMode:'remote',
store: Ext.data.StoreManager.get('ContrAgents')
},
renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
store_ca = Ext.data.StoreManager.get('ContrAgents');
if (record.data.contragent != ''){
store_ca.load();
var contr = store_ca.getById(record.data.contragent);
//indexInStore = store_ca.findExact('id', value);
console.log(contr);
return contr.data.name;
}
}
},
...
Store 'ContrAgents' looks like this:
Ext.define('BookApp.store.ContrAgents', {
extend: 'Ext.data.Store',
model: 'BookApp.model.ContrAgents',
autoLoad: true,
proxy: {
type: 'ajax',
url: 'app/data/Contragents.json'
}
});
The problem is that the name of the required field is not returned (contr.data.name), contr is null.
Apparently the store does not have time to load, in this case I need to load it, but store_ca.load () does not bring results.
How to load the store correctly to use
store_ca.getById (record.data.contragent); to return the name of the field?
I'm not entirely sure why you would need to use a store to populate the value in the grid's cell, because you could always just send the text value through the grid's store as opposed to the id.
You probably have a good reason for doing it, so I've revisited the fiddle and implemented it accordingly. You should be able to check it out here
The changes
../app/store/ContrAgents.js
Ext.define('Fiddle.store.ContrAgents', {
extend: 'Ext.data.Store',
model: 'Fiddle.model.ContrAgents',
autoLoad: false,
proxy: {
type: 'ajax',
url: 'Contragents.json'
}
});
../app/store/ListViewStore.js
Ext.define('Fiddle.store.ListViewStore', {
extend: 'Ext.data.Store',
model: 'Fiddle.model.ListViewModel',
autoLoad: false,
proxy: {
type: 'ajax',
url: 'List.json'
}
});
../app/view/ListView.js
Ext.define('Fiddle.view.ListView' ,{
extend: 'Ext.grid.Panel',
alias: 'widget.booklist',
itemId: 'BookList',
store: 'ListViewStore',
xtype: 'listview',
plugins: 'gridfilters',
initComponent: function() {
var me = this;
// Pin an instance of the store on this grid
me.myContrAgents = Ext.data.StoreManager.lookup('ContrAgents');
// Manually load the 'ContrAgents' first
me.myContrAgents.load(function(records, operation, success) {
// Now load the 'ListViewStore' store
me.getStore().load();
});
me.columns = [
{
header: 'ID',
dataIndex: 'id',
sortable: true,
width: 35
},
{
text: 'Контрагент',
dataIndex: 'contragent',
renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
if (value > 0) {
if (rec = me.myContrAgents.findRecord('id', value)) {
return rec.get('name');
}
}
return '';
}
}
];
me.callParent(arguments);
}
});
Data/List.json
"data" : [
{"id": 1, "contragent": "2"},
{"id": 2, "contragent": "3"},
{"id": 3, "contragent": "4"}
]
You are trying to query the store before it's data is actually populated. You want to avoid loading the store for each time the renderer event is triggered.
The Ext.data.Store->load function is asynchronous
See docs
store.load({
scope: this,
callback: function(records, operation, success) {
// the operation object
// contains all of the details of the load operation
console.log(records);
}
});
Change your implementation to this and test if it works
renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
store_ca = Ext.data.StoreManager.get('ContrAgents');
if (record.data.contragent != ''){
store_ca.load(function(records, operation, success) {
console.log('loaded records');
var contr = store_ca.getById(record.data.contragent);
indexInStore = store_ca.findExact('id', value);
console.log({
contr: contr,
indexInStore: indexInStore
});
});
// Not sure what you are trying to return here
// return contr.data.name;
}
}
Remote Combo in ExtJS Grid Example
I found a good example for what you are trying to accomplish here with a combo dropdown in a grid with a remote store, check out this post (you might have to register for free but the solution is worth it and I won't plagiarise it here)
Grid with combo edit widget with binding
Perhaps this could help...
Ext.define('Fiddle.view.ListView' ,{
extend: 'Ext.grid.Panel',
alias: 'widget.booklist',
itemId: 'BookList',
store: 'ListViewStore',
xtype: 'listview',
plugins: ['cellediting','gridfilters'],
initComponent: function() {
this.columns = [
{
header: 'ID',
dataIndex: 'id',
sortable: true,
width: 35
},
{
text: 'Контрагент',
width: 150,
xtype: 'widgetcolumn',
dataIndex: 'contragent',
widget: {
xtype: 'combo',
bind: '{record.contragent}',
allowBlank: false,
displayField: 'name',
valueField: 'id',
store: Ext.data.StoreManager.lookup('ContrAgents')
}
}
];
this.callParent(arguments);
}
});

Add filter to a Extjs grid

I want to add some filters to my grid when I clicked on the filter button (see below).The filters must be add tot the grid with the given values from the filterpanel form.
On this page the textfield will be filled in and when I clicked on the filter button the onFilterClick handler will trigger the FilterController.
Ext.define('path.to.filterPanel', {
extend: 'Ext.form.Panel',
xtype: 'filter',
controller: 'dashboard-filter',
viewModel: {
type: 'dashboard-filter'
},
frame: false,
bodyPadding: 10,
layout: 'form',
// the fields
items: [{
xtype: 'textfield',
name: 'firstName',
id: 'firstName',
fieldLabel: 'Firstname'
}, {
xtype: 'textfield',
name: 'lastName',
id: 'lastName',
fieldLabel: 'Lastname'
}],
buttons: [
text : 'Filter',
handler: 'onFilterClick' // trigger to the controller
}]
});
When clicked on the Filterbutton the values will be pust to this controller.
Ext.define('path.to.FilterController', {
extend: 'Ext.app.ViewController',
alias: 'controller.dashboard-filter',
onFilterClick: function(button) {
var form = button.up('form').getForm();
if (form.isValid()) {
// form valid
var firstName = Ext.getCmp("firstName").getValue();
var lastName = Ext.getCmp("lastName").getValue();
// check if there is some input
if (!!employeeNumber || !!firstName || !!lastName) {
// add filters but how??
}
} else {
// form not valid
console.log("not valid");
}
}
});
Finally this is the grid file where the filters must be added.
Ext.define('path.to.gridPanel, {
extend: 'Ext.grid.Panel',
requires: [
'Ext.grid.column.Action',
'App.store.Employees'
],
controller: 'dashboard-gridVieuw',
xtype: 'gridVieuw',
store: 'Employees',
stateful: true,
collapsible: true,
multiSelect: true,
stateId: 'gridController',
initComponent: function () {
this.store = new App.store.Employees();
var me = this;
me.columns = [{
header : 'Firstname',
flex : 1,
sortable : true,
dataIndex: 'firstName'
}, {
header : 'Lastname',
flex : 1,
sortable : true,
dataIndex: 'lastName'
}]
me.callParent();
}
});
I use version 5 of extjs.
You can use filterBy method to filter the underlying store associated with the grid. Here is an example:
Ext.getStore('myStore').filterBy(function(record, id) {
if (record.get('firstName') === firstName) {
return true;
} else {
return false;
}
});
Here is a fiddle demonstrating a working example of a filter
Thanks for answering my question. It works for me. I've added the follow OnClick handler in the controller.
onFilterClick: function(button) {
var form = button.up('form').getForm();
if (form.isValid()) {
var fieldOne = Ext.getCmp("fieldOne").getValue();
var fieldTwo = Ext.getCmp("fieldTwo").getValue();
// check if there is some input
if (!!fieldOne || !!fieldTwo) {
// get store
var store = Ext.data.StoreManager.lookup('store');
// check if store not empty
if(!Ext.isEmpty(store)){
// clear filters and add a few new filters if params not empty
store.clearFilter(true);
if (!Ext.isEmpty(fieldOne)) {
store.filter("fieldOne", fieldOne)
}
if (!Ext.isEmpty(fieldTwo)) {
store.filter("fieldTwo", fieldTwo)
}
// count the filtered rows
var count = store.snapshot ? store.snapshot.length : store.getCount();
if (count == 0) {
alert("No data found!");
store.clearFilter();
}
} else{
//Store empty
console.warn("Store's empty");
}
} else {
// no values
alert("No entered data!");
}
} else {
// form not valid
alert("Form not valid!");
}
}

EnhancedGrid within another EnhancedGrid

I'm using the dojo grid cell formatter to display an inner grid within another grid. Even though the inner grid is added, it does not display on the HTML page cause its height and width are 0px.
My JSP page and the JS page where the grids are created is shown below. Any help will be appreciated.
My guess is that calling grid.startup() in the cell formatter is probably not the right place. But where should I move the startup() call to -or- is there something else that needs to be done to get the inner grid to render correctly.
----JSP file ----
<script type="text/javascript"> dojo.require("portlets.ListPortlet"); var <portlet:namespace/>args = { namespace: '<portlet:namespace/>', listDivId: 'listGrid' }; var <portlet:namespace/>controller = new portlets.ListPortlet(<portlet:namespace/>args); dojo.addOnLoad(function(){ <portlet:namespace/>controller.init(); }); </script>
</div>
----JS file ----
dojo.declare("portlets.ListPortlet", null, {
constructor: function(args){
dojo.mixin(this,args);
this.params = args;
},
init: function(){
var layout = [[
{field: 'site', name: 'Site', width: '30px' }
{field: 'name', name: 'Full Name', width: '100px'},
{field: 'recordStatus', name: 'Status', width: '80px' }
],[
{field: '_item', name: ' ', filterable: false, formatter: this.formatNotesTable, colSpan: 3 }
]];
this.grid = new dojox.grid.EnhancedGrid({
autoHeight: true,
autoWidth: true,
selectable: true,
query:{
fromDate: start,
toDate: end
},
rowsPerPage: 10
});
this.grid.placeAt(dojo.byId(this.listDivId));
this.dataStore = new dojox.data.JsonRestStore({target: dataURL, idAttribute: idAttribute});
this.grid.setStructure(layout);
this.grid.startup();
},
formatNotesTable(rowObj) {
var gridData = {
identifier:"id",
items: [
{id:1, "Name":915,"IpAddress":6},
{id:2, "Name":916,"IpAddress":7}
]
};
var gridStructure = [{
cells:[
[
{ field: "Name",
name: "Name",
},
{ field: "IpAddress",
name: "Ip Address" ,
styles: 'text-align: right;'
}
]
]
}];
var gridStore = new dojo.data.ItemFileReadStore( { data: gridData} );
var cpane = new dijit.layout.ContentPane ({
content: "inner grid should be displayed below"
});
var subgrid = new dojox.grid.DataGrid({
store: gridStore,
structure: gridStructure,
style: {width: "325px", height: "300px"}
});
subgrid.placeAt(cpane);
subgrid.startup();
return cpane;
}
}
I solved my problem by using a dojox.layout.TableContainer inside the EnhancedGrid.

html with value from function

Im really new to this java script and Sencha touch so sorry if this question is simple but I didn't find a way to do this.
I have a function that returns a value .
now I want to display the value from the function inside an html line.
how im calling this function from html element? how I show the value?
config: {
title: 'Me',
iconCls: 'user',
//layout: 'fit',
/* create a title bar for the tab panel */
items: [
{
docked: 'top',
xtype: 'titlebar',
title: 'Singers'
},
{
html: [
'<h1>Hi!! ' + this.setName +'</h1>'
].join("")
}
],
},
setName: function (val) {
var store =Ext.getStore('user');
var last = st.last('user');
return val=(last.get('user'));
}
});
You could use the itemId property to reference the component and then update its html property via the initialise listener.
For example:
config: {
title: 'Me',
iconCls: 'user',
//layout: 'fit',
/* create a title bar for the tab panel */
items: [
{
docked: 'top',
xtype: 'titlebar',
title: 'Singers'
},
{
itemId:'htmlContainer', //use itemIds like div ids
xtype:'container',
html: ''
}
],
/**
* #cfg listeners
* Parent Component listeners go here.
* Check sencha touch doc for more information and other available events.
*/
listeners:{
initialize: 'onInitialise'
}
},
onInitialise: function()
{
//set the html config item here
var c = this.down('#htmlContainer');
c.setHtml(this.setName());
},
setName: function (val) {
....
}

ExtJS - Complex form serialization

I have a complex form which I can't use form serialization technique. There are many fields as well as dynamic grid ( the grid dynamically generating upon user choosing some criteria ) inside the form.
What I want to do, collect user inputs/selections + adding selected records that available in the grid then finally making a JSON array with those datas to be able to post server side.
My guess, I can use getCmp function of the ExtJS to take whole datas but as you might guess this way little bit hard to maintain. Also, I have no idea to get grid data with this way!
PS : Code is little bit long so that I cropped some parts.
MODEL AND STORE DEFINITIONS
Ext.Loader.setConfig({enabled: true});
Ext.Loader.setPath('Ext.ux', '<?php echo js_url(); ?>resources/ux');
Ext.require([
'Ext.grid.*',
'Ext.data.*',
'Ext.form.*',
'Ext.state.*',
'Ext.util.*',
'Ext.layout.container.Column',
'Ext.selection.CheckboxModel',
'Ext.ux.RowExpander',
'Ext.ux.statusbar.StatusBar'
]);
var navigate = function (panel, direction) {
var layout = panel.getLayout();
layout[direction]();
Ext.getCmp('move-prev').setDisabled(!layout.getPrev());
Ext.getCmp('move-next').setDisabled(!layout.getNext());
};
// Article Model
Ext.define('Article', {
extend: 'Ext.data.Model',
fields: [
{name: 'ARTICLE_ID', type: 'int'},
{name: 'DESCRIPTION', type: 'string'}
]
});
// Article Json Store
var articles = new Ext.data.JsonStore({
model: 'Article',
proxy: {
type: 'ajax',
url: '<?php echo base_url() ?>create/get_articles',
reader: {
type: 'json',
root: 'artList',
idProperty: 'ARTICLE_ID'
}
}
});
winArticle = new Ext.Window({
width: 600,
modal: true,
title: 'Artikel Seçimi',
closeAction: 'hide',
bodyPadding: 10,
items: new Ext.Panel({
items: [
{
xtype: 'fieldset',
title: 'Artikel Sorgulama',
defaultType: 'textfield',
layout: 'anchor',
defaults: {
anchor: '100%'
},
height: '76px',
items: [
{
xtype: 'fieldcontainer',
layout: 'hbox',
defaultType: 'textfield',
items: [
{
xtype: 'combobox',
id: 'articleNo',
inputWidth: 320,
fieldLabel: 'ARTİKEL NO',
fieldStyle: 'height: 26px',
margin: '10 15 0 0',
triggerAction: 'query',
pageSize: true
},
{
xtype: 'button',
text: 'SORGULA',
width: 100,
scale: 'medium',
margin: '8 0 0 0'
}
]
}
]
},
{
xtype: 'fieldset',
title: 'Artikel Bilgileri',
height: '140px',
layout: 'fit',
items: [
{
xtype: 'fieldcontainer',
layout: 'hbox',
defaultType: 'textfield',
fieldDefaults: {
labelAlign: 'top'
},
items: [
{
fieldLabel: 'ARTİKEL TANIMI',
name: 'artDesc',
flex: 3,
margins: '0 5 0 0'
},
{
fieldLabel: 'PAKET İÇERİĞİ',
name: 'artgebi',
flex: 1
}
]
},
{
xtype: 'fieldcontainer',
layout: 'hbox',
defaultType: 'textfield',
id: 'artContainer1',
fieldDefaults: {
labelAlign: 'top'
},
items: [
{
fieldLabel: 'SUBSYS',
name: 'artSubsys',
flex: 1,
margins: '0 5 0 0'
},
{
fieldLabel: 'VARIANT',
name: 'artVariant',
flex: 1,
margins: '0 5 0 0'
},
{
fieldLabel: 'VARIANT TANIMI',
name: 'artVariantDesc',
flex: 2
}
]
}
]
},
{
xtype: 'fieldset',
title: 'Aksiyon Seviyeleri',
id: 'article-fieldset',
items: [
{
xtype: 'button',
id: 'btnArticleLevelAdd',
text: 'LEVEL EKLE',
scale: 'medium',
width: 100,
style: 'float: right',
margin: '0 7 0 0',
handler: function() {
var getLevels = function() {
var count = winArticle.down('fieldset[id=article-fieldset]').items.items.length;
return count;
}
var count = getLevels();
if (count === 3) {
Ext.getCmp('btnArticleLevelAdd').disable();
}
var container = 'artContainer' + count;
//console.log(container);
Ext.getCmp('article-fieldset').add([
{
xtype: 'fieldcontainer',
layout: 'hbox',
id: 'artContainer' + count,
defaultType: 'textfield',
fieldDefaults: {
labelAlign: 'top'
},
items: [
{
name: 'artLevel' + count,
allowBlank: false,
inputWidth: 216,
fieldStyle: 'text-align: right; font-size: 13pt; background-color: #EAFFCC;',
margins: '0 5 5 0'
},
{
name: 'artValue' + count,
allowBlank: false,
inputWidth: 216,
fieldStyle: 'text-align: right; font-size: 13pt; background-color: #EAFFCC;',
margins: '0 5 0 0'
},
{
xtype: 'button',
text: 'SİL',
width: 40,
cls: 'btn-article-remove',
handler: function() {
if(count <= 3) {
Ext.getCmp('btnArticleLevelAdd').enable();
} else {
Ext.getCmp('btnArticleLevelAdd').disable();
}
winArticle.down('fieldset[id=article-fieldset]').remove(container);
}
}
]
}
]);
}
},
{
xtype: 'fieldcontainer',
layout: 'hbox',
id: 'article-level-container',
defaultType: 'textfield',
fieldDefaults: {
labelAlign: 'top'
},
items: [
{
fieldLabel: 'LEVEL',
name: 'artLevel',
inputWidth: 216,
margins: '0 5 5 0',
allowBlank: false,
fieldStyle: 'text-align: right; font-size: 13pt; background-color: #EAFFCC;'
},
{
fieldLabel: 'VALUE',
name: 'artValue',
inputWidth: 216,
allowBlank: false,
blankText: 'zorunlu alan, boş bırakılamaz',
fieldStyle: 'text-align: right; font-size: 13pt; background-color: #EAFFCC;',
listeners: {
change: function(textfield, newValue, oldValue) {
if(oldValue == 'undefined' || newValue == '') {
Ext.getCmp('btnArticleSave').disable();
} else {
Ext.getCmp('btnArticleSave').enable();
}
}
}
}
]
}
]
}
]
}),
buttons: [
{
text: 'KAPAT',
scale: 'medium',
width: 100,
cls: 'btn-article-close',
listeners: {
click: function() {
winArticle.close();
}
}
},
'->',
{
text: 'EKLE',
scale: 'medium',
disabled: true,
width: 100,
margin: '0 9 0 0',
cls: 'btn-article-save',
id: 'btnArticleSave'
}
]
});
EXT.ONREADY FUNCTION
Ext.onReady(function () {
Ext.QuickTips.init();
Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
expires: new Date(new Date().getTime() + (1000 * 60 * 60 * 24 * 7))
}));
var Discounts = Ext.create('Ext.form.Panel', {
id: 'discount-types',
bodyPadding: 10,
width: 760,
height: 600,
title: 'DNR TANIMLAMA / SCREEN 0',
layout: 'card',
bodyStyle: 'padding:20px',
defaults: {
border: false,
anchor: '100%'
},
style: {
'box-shadow': '0 2px 5px rgba(0, 0, 0, 0.6)',
'-webkit-box-shadow': '0 0 8px rgba(0, 0, 0, 0.5)'
},
frame: true,
buttons: [
{
text: 'ÖNCEKİ ADIM',
id: 'move-prev',
cls: 'np-button',
scale: 'medium',
iconCls: 'dnr-prev-icon',
iconAlign: 'left',
handler: function (btn) {
navigate(btn.up('panel'), 'prev');
var itemd = Discounts.getLayout().getActiveItem();
Discounts.setTitle('DNR TANIMLAMA ' + ' / ' + itemd.cardTitle);
Ext.getCmp('dnr-submit').disable();
Ext.getCmp('dnr-submit').setVisible(false);
},
disabled: true
},
{
text: 'SONRAKİ ADIM',
id: 'move-next',
scale: 'medium',
cls: 'np-button',
iconCls: 'dnr-next-icon',
iconAlign: 'right',
handler: function (btn) {
navigate(btn.up('panel'), 'next');
var itemd = Discounts.getLayout().getActiveItem();
Discounts.setTitle('DNR TANIMLAMA ' + ' / ' + itemd.cardTitle);
var cardNum = Discounts.items.indexOf(itemd);
if (cardNum == 3) {
Ext.getCmp('dnr-submit').enable();
Ext.getCmp('dnr-submit').setVisible(true);
}
},
disabled: true
},
'->',
{
text: '&nbsp KAYDET ',
id: 'dnr-submit',
scale: 'medium',
iconCls: 'dnr-submit-icon',
iconAlign: 'right',
cls: 'dnr-submit',
disabled: true,
hidden: true,
handler: function (btn) {
}
}
],
items: [
{
id: 'screen-0',
cardTitle: 'SCREEN 0',
layout: 'form',
items: [
{
layout: {
type: 'vbox',
align: 'center'
},
margin: '60 0 0 0',
items: [
{
xtype: 'combobox',
inputWidth: 295,
fieldLabel: 'DNR TİPİ',
fieldStyle: 'height: 26px',
id: 'discount-type',
store: discounts,
valueField: 'DNR_TYPE_ID',
displayField: 'DNR_TYPE_DESC',
queryMode: 'remote',
forceSelection: true,
stateful: true,
stateId: 'cmb_disc_type',
allowBlank: false,
emptyText: 'DNR tipini seçiniz...',
triggerAction: 'all',
listeners: {
select: function (e) {
var discType = Ext.getCmp('discount-type').getValue();
var discDetail = Ext.getCmp('discount-detail');
discdetails.removeAll();
if (discType != 0) {
discDetail.setDisabled(false);
discdetails.proxy.extraParams = { 'dnrtype': discType };
discdetails.load();
}
}
}
},
{
xtype: 'combobox',
inputWidth: 400,
fieldStyle: 'height: 26px',
id: 'discount-detail',
valueField: 'ID',
displayField: 'DNR_TITLE',
store: discdetails,
forceSelection: true,
stateful: true,
stateId: 'cmb_disc_detail',
margin: '25 0 0 0',
disabled: true,
allowBlank: false,
msgTarget: 'side',
emptyText: 'İNDİRİM TİPİNİ SEÇİNİZ...',
blankText: 'İndirim tipi boş olamaz!',
triggerAction: 'all',
listeners: {
select: function (e) {
var discDetail = Ext.getCmp('discount-detail').getValue();
if (discDetail != 'null') {
var value = discdetails.getAt(discdetails.find('ID', discDetail)).get('DNR_DESCRIPTION');
Ext.getCmp('dnr-type-desc-panel').setVisible(true);
Ext.getCmp('dnr-type-desc-panel').update(value);
}
}
}
},
{
xtype: 'textarea',
grow: false,
name: 'invoiceText',
fieldLabel: 'FATURA METNİ',
id: 'invoice-text',
blankText: 'Fatura metni boş olamaz!',
width: 400,
height: 60,
margin: '30 0 0 0',
allowBlank: false,
msgTarget: 'side',
listeners: {
change: function (e) {
if (Ext.getCmp('invoice-text').getValue().length === 0) {
Ext.getCmp('move-next').disable();
} else {
Ext.getCmp('move-next').enable();
}
}
}
},
{
xtype: 'panel',
id: 'dnr-type-desc-panel',
layout: {type: 'hbox', align: 'stretch'},
height: 145,
width: 400,
cls: 'dnr-desc-panel',
margin: '60 0 0 0',
html: '&nbsp',
hidden: true
}
]
}
]
},
{
id: 'screen-1',
cardTitle: 'SCREEN 1',
layout: 'form',
items: [
{
layout: 'column',
width: 730,
height: 90,
items: [
{
xtype: 'fieldset',
title: 'ARTİKEL / HEDEF GRUP / MAL GRUBU SEÇİMİ',
cls: 'dnr-fieldset',
width: 730,
height: 80,
margin: '0',
items: [
{
xtype: 'buttongroup',
columns: 5,
columnWidth: 140,
frame: false,
margin: '5 0 0 18',
items: [
{
text: 'ARTİKEL',
scale: 'medium',
margin: '0 18px 0 0',
width: 120,
height: 36,
id: 'btn-article',
cls: 'btn-grp-choose btn-grp-article',
listeners: {
click: function () {
winArticle.center();
winArticle.show();
}
}
},
{
text: 'PUAR',
scale: 'medium',
margin: '0 18px 0 0',
width: 120,
height: 36,
cls: 'btn-grp-choose btn-grp-puar',
listeners: {
click: function() {
winPuar.show();
}
}
},
{
text: 'MAL GRUBU',
scale: 'medium',
margin: '0 18px 0 0',
width: 120,
height: 36,
cls: 'btn-grp-choose btn-grp-choose',
listeners: {
click: function() {
winArticleGroup.show();
}
}
},
{
text: 'HEDEF GRUP',
scale: 'medium',
margin: '0 18px 0 0',
width: 120,
height: 36,
cls: 'btn-grp-choose btn-grp-target',
listeners: {
click: function() {
winTargetGroup.show();
}
}
},
{
text: 'SUPPLIER',
scale: 'medium',
width: 120,
height: 36,
cls: 'btn-grp-choose btn-grp-supplier',
listeners: {
click: function() {
winSupplier.show();
}
}
}
]
}
]
}
]
},
{
xtype: 'gridpanel',
id: 'article-grid',
selType: 'rowmodel',
elStatus: true,
plugins: [
{ ptype: 'cellediting', clicksToEdit: 1},
{ ptype: 'datadrop'}
],
/* ***************************************************************
* here is the tricky part! when user change any fields above
* this grid will dynamically generate upon user request. So that
* we arent sure which columns will be available.
* ***************************************************************/
columns: [
{
text: 'COLUMN A',
dataIndex: ''
}
]
}
]
},
renderTo: 'content'
})
});
Updated answer
After some clarification I think the answer should be quite easy (at least I think so) For the following answer I assume that you are inside the form at the time you want to fetch the form & grid data and that there is only one Ext.form.Panel:
// Navigate up to the form:
var form = this.up('form'),
// get the form values
data = form.getValues(),
// get the selected record from the grid
gridRecords = form.down('grid').getSelectionModel().getSelected(),
// some helper variables
len = gridRecords.length,
recordData = [];
// normalize the model data by copying just the data objects into the array
for(i=0;i<len;i++) {
recordData .push(gridRecords[i].data);
}
// apply the selected grid records to the formdata. For that you will need a property name, I will use just 'gridRecords' but you may change it
data.gridRecords = recordData;
// send all back via a ajax request
Ext.Ajax.request({
url: 'demo/sample',
success: function(response, opts) {
// your handler
},
failure: function(response, opts) {
// your handler
},
jsonData: data
});
That should it be
To provide some more options of data that can be fetched from/by the grid
// get all data that is currently in the store
form.down('grid').getStore().data.items
// get all new and updated records
form.down('grid').getStore().getModifiedRecords()
// get all new records
form.down('grid').getStore().getNewRecords()
// get all updated records
form.down('grid').getStore().getUpdatedRecords()
Old answer (for more complex scenarios) below
What you told:
You have a grid with forms and maybe grids. Where you need to also
readout the grids when fetching the data from the form.
In the answer below I will just cover getValues, binding/unbinding events to each grid and not
form load/submit
record load/update
setting values
My recommendation is to make your form much more intelligent so that it is capable of handling this.
What do I mean by?
A default form cares about all fields that are inserted anywhere in it's body. In 99,9% this is perfectly fine but not for all. Your form also need to take care about grids that get inserted.
How it can be done
First thing is that when you make your Grids parts of a form I recommend to give them a name property. Second is that you need to know how a form gather and utilizes the fields so that you be able to copy that for grids. For that you need to take a look at the Ext.form.Basic class constructor where the important part is this
// We use the monitor here as opposed to event bubbling. The problem with bubbling is it doesn't
// let us react to items being added/remove at different places in the hierarchy which may have an
// impact on the dirty/valid state.
me.monitor = new Ext.container.Monitor({
selector: '[isFormField]',
scope: me,
addHandler: me.onFieldAdd,
removeHandler: me.onFieldRemove
});
me.monitor.bind(owner);
What happens here is that a monitor get initialized that from this on will look for any field that get inserted into the bound componenent where the monitor will call the appropriate handler. Currently the monitor is looking for fields but you will need one that is looking for grids. Such a monitor would look like:
me.gridMonitor = new Ext.container.Monitor({
selector: 'grid',
scope: me,
addHandler: me.onGridAdd,
removeHandler: me.onGridRemove
});
me.gridMonitor.bind(owner);
Because I don't know much about your datastructure I cannot tell you which gridevents you might need but you should register/unregister them in the addHandler/removeHandler like
onGridAdd: function(grid) {
var me = this;
me.mon(grid,'select',me.yourHandler,me);
},
onGridRemove: function(grid) {
var me = this;
me.mun(grid,'select',me.yourHandler,me);
}
In addition you will need the following helper methods
/**
* Return all the {#link Ext.grid.Panel} components in the owner container.
* #return {Ext.util.MixedCollection} Collection of the Grid objects
*/
getGrids: function() {
return this.gridMonitor.getItems();
},
/**
* Find a specific {#link Ext.grid.Panel} in this form by id or name.
* #param {String} id The value to search for (specify either a {#link Ext.Component#id id} or
* {#link Ext.grid.Panel name }).
* #return {Ext.grid.Panel} The first matching grid, or `null` if none was found.
*/
findGrid: function(id) {
return this.getGrids().findBy(function(f) {
return f.id === id || f.name === id;
});
},
And most important the method that get's the data from the grids. Here we need to override
getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
var values = {},
fields = this.getFields().items,
grids = this.getGrids().items, // the grids found by the monitor
f,
fLen = fields.length,
gLen = grids.length, // gridcount
isArray = Ext.isArray,
grid, gridData, gridStore, // some vars used while reading the grid content
field, data, val, bucket, name;
for (f = 0; f < fLen; f++) {
field = fields[f];
if (!dirtyOnly || field.isDirty()) {
data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);
if (Ext.isObject(data)) {
for (name in data) {
if (data.hasOwnProperty(name)) {
val = data[name];
if (includeEmptyText && val === '') {
val = field.emptyText || '';
}
if (values.hasOwnProperty(name)) {
bucket = values[name];
if (!isArray(bucket)) {
bucket = values[name] = [bucket];
}
if (isArray(val)) {
values[name] = bucket.concat(val);
} else {
bucket.push(val);
}
} else {
values[name] = val;
}
}
}
}
}
}
// begin new part
for (g = 0; g < gLen; g++) {
grid = grids[f];
gridStore = grid.getStore();
gridData = [];
// You will need a identification variable to determine which data should be taken from the grid. Currently this demo implement three options
// 0 only selected
// 1 complete data within the store
// 2 only modified records (this can be splitted to new and updated)
var ditems = grid.submitData === 0 ? grid.getSelectionModel().getSelection() :
grid.submitData === 1 ? gridStore.getStore().data.items : gridStore.getStore().getModifiedRecords(),
dlen = ditems.length;
for(d = 0; d < dLen; d++) {
// push the model data to the current data list. It doesn't matter of which type the models (records) are, this will simply read the whole known data. Alternatively you may access the rawdata property if the reader does not know all fields.
gridData.push(ditems[d].data);
}
// assign the array of record data to the grid-name property
data[grid.name] = gridData;
}
// end new part
if (asString) {
values = Ext.Object.toQueryString(values);
}
return values;
}
Clued together should it look something like
Ext.define('Ext.ux.form.Basic', {
extend: 'Ext.form.Basic',
/**
* Creates new form.
* #param {Ext.container.Container} owner The component that is the container for the form, usually a {#link Ext.form.Panel}
* #param {Object} config Configuration options. These are normally specified in the config to the
* {#link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically.
*/
constructor: function(owner, config) {
var me = this;
me.callParent(arguments);
// We use the monitor here as opposed to event bubbling. The problem with bubbling is it doesn't
// let us react to items being added/remove at different places in the hierarchy which may have an
// impact on the dirty/valid state.
me.gridMonitor = new Ext.container.Monitor({
selector: 'grid',
scope: me,
addHandler: me.onGridAdd,
removeHandler: me.onGridRemove
});
me.gridMonitor.bind(owner);
},
onGridAdd: function(grid) {
var me = this;
me.mon(grid,'select',me.yourHandler,me);
},
onGridRemove: function(grid) {
var me = this;
me.mun(grid,'select',me.yourHandler,me);
},
/**
* Return all the {#link Ext.grid.Panel} components in the owner container.
* #return {Ext.util.MixedCollection} Collection of the Grid objects
*/
getGrids: function() {
return this.gridMonitor.getItems();
},
/**
* Find a specific {#link Ext.grid.Panel} in this form by id or name.
* #param {String} id The value to search for (specify either a {#link Ext.Component#id id} or
* {#link Ext.grid.Panel name }).
* #return {Ext.grid.Panel} The first matching grid, or `null` if none was found.
*/
findGrid: function(id) {
return this.getGrids().findBy(function(f) {
return f.id === id || f.name === id;
});
},
getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
var values = {},
fields = this.getFields().items,
grids = this.getGrids().items, // the grids found by the monitor
f,
fLen = fields.length,
gLen = grids.length, // gridcount
isArray = Ext.isArray,
grid, gridData, gridStore, // some vars used while reading the grid content
field, data, val, bucket, name;
for (f = 0; f < fLen; f++) {
field = fields[f];
if (!dirtyOnly || field.isDirty()) {
data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);
if (Ext.isObject(data)) {
for (name in data) {
if (data.hasOwnProperty(name)) {
val = data[name];
if (includeEmptyText && val === '') {
val = field.emptyText || '';
}
if (values.hasOwnProperty(name)) {
bucket = values[name];
if (!isArray(bucket)) {
bucket = values[name] = [bucket];
}
if (isArray(val)) {
values[name] = bucket.concat(val);
} else {
bucket.push(val);
}
} else {
values[name] = val;
}
}
}
}
}
}
// begin new part
for (g = 0; g < gLen; g++) {
grid = grids[f];
gridStore = grid.getStore();
gridData = [];
// You will need a identification variable to determine which data should be taken from the grid. Currently this demo implement three options
// 0 only selected
// 1 complete data within the store
// 2 only modified records (this can be splitted to new and updated)
var ditems = grid.submitData === 0 ? grid.getSelectionModel().getSelection() :
grid.submitData === 1 ? gridStore.getStore().data.items : gridStore.getStore().getModifiedRecords(),
dlen = ditems.length;
for(d = 0; d < dLen; d++) {
// push the model data to the current data list. It doesn't matter of which type the models (records) are, this will simply read the whole known data. Alternatively you may access the rawdata property if the reader does not know all fields.
gridData.push(ditems[d].data);
}
// add the store data as array to the grid-name property
data[grid.name] = gridData;
}
// end new part
if (asString) {
values = Ext.Object.toQueryString(values);
}
return values;
}
});
Next is to modify the form to use this basic form type
Ext.define('Ext.ux.form.Panel', {
extend:'Ext.form.Panel',
requires: ['Ext.ux.form.Basic'],
/**
* #private
*/
createForm: function() {
var cfg = {},
props = this.basicFormConfigs,
len = props.length,
i = 0,
prop;
for (; i < len; ++i) {
prop = props[i];
cfg[prop] = this[prop];
}
return new Ext.ux.form.Basic(this, cfg);
}
});
Note:
This is all untested! I've done something similar for various
customers to extend the capabilities of forms and I can tell tell that
this way will work very well and fast. At least it should show how it can be done and it can easily be tweaked to also set forms and/or load/update records.
I have not used this myself, but there is a thread that tries to deal with associated models via hasMany relationship. The problem is that everyone has slightly different expectations of what should happen during write operation of records. Serever side ORMs deal with this issue in somewhat difficult to understand manner and is often a sore spot for new developers.
Here is the forum thread that details a custom JSON writer to persist a parent record with it's children records.
Here is the code that seems to work at least for some folks:
Ext.data.writer.Json.override({
/*
* This function overrides the default implementation of json writer. Any hasMany relationships will be submitted
* as nested objects. When preparing the data, only children which have been newly created, modified or marked for
* deletion will be added. To do this, a depth first bottom -> up recursive technique was used.
*/
getRecordData: function(record) {
//Setup variables
var me = this, i, association, childStore, data = record.data;
//Iterate over all the hasMany associations
for (i = 0; i < record.associations.length; i++) {
association = record.associations.get(i);
data[association.name] = null;
childStore = record[association.storeName];
//Iterate over all the children in the current association
childStore.each(function(childRecord) {
if (!data[association.name]){
data[association.name] = [];
}
//Recursively get the record data for children (depth first)
var childData = this.getRecordData.call(this, childRecord);
/*
* If the child was marked dirty or phantom it must be added. If there was data returned that was neither
* dirty or phantom, this means that the depth first recursion has detected that it has a child which is
* either dirty or phantom. For this child to be put into the prepared data, it's parents must be in place whether
* they were modified or not.
*/
if (childRecord.dirty | childRecord.phantom | (childData != null)){
data[association.name].push(childData);
record.setDirty();
}
}, me);
/*
* Iterate over all the removed records and add them to the preparedData. Set a flag on them to show that
* they are to be deleted
*/
Ext.each(childStore.removed, function(removedChildRecord) {
//Set a flag here to identify removed records
removedChildRecord.set('forDeletion', true);
var removedChildData = this.getRecordData.call(this, removedChildRecord);
data[association.name].push(removedChildData);
record.setDirty();
}, me);
}
//Only return data if it was dirty, new or marked for deletion.
if (record.dirty | record.phantom | record.get('forDeletion')){
return data;
}
}
});
The full thread is located here:
http://www.sencha.com/forum/showthread.php?141957-Saving-objects-that-are-linked-hasMany-relation-with-a-single-Store/page5

Categories

Resources