PDA

View Full Version : Comboboxs that are dependent on value from others in a Grid



Cannon
27 Jun 2010, 11:12 AM
i am constucting an ediatble grid to log time spent on specific projects.

it has fields for date and time, as well as the job that was worked on.

Jobs belong to a project, which belong to a client

I need a way to create this behavior in my grid

a user selects a client from the clients combo box
the projects combo-box should now only contain projects that belong to that client
when they select a proejct i want the jobs lsit to do the same.

i was able to achieve this with a on("change"...) listener, but it rerenders every record in my grid, causing their values to not be displayed

is there a way to only manipulate one row's editor without screwing with all of them?



a screen shot :
21127
and heres the source :




// Application instance for showing user-feedback messages.
var App = new Ext.App({});

// Create a standard HttpProxy instance.
var proxy = new Ext.data.HttpProxy({
api: {
read: {
url: '/entries.xml',
method: 'GET'
}, create: {
url: '/entries.xml',
method: 'POST'
}, update: {
url: '',
method: 'PUT'
}, destroy: {
url: '',
method: 'DELETE'
}
}
});
var reader = new Ext.data.XmlReader({
record: 'entry',
id: 'id'
}, [{
name: 'day',
type: 'date',
dateFormat: 'Y-m-d'
}, {
name: 'job-id'
}, {
name: 'project-id'
}, {
name: 'client-id'
}, {
name: 'time',
type: 'float'
}, {
name: 'submitted',
type: 'boolean'
}]);
// The new DataWriter component.
var writer = new Ext.data.XmlWriter({
root: 'entries'

});
// Typical Store collecting the Proxy, Reader and Writer together.
var store = new Ext.data.GroupingStore({
//restful: true,
// <-- This Store is RESTful
proxy: proxy,
reader: reader,
writer: writer,
// <-- plug a DataWriter into the store just as you would a Reader
sortInfo: {
field: 'day',
direction: "DSC"
}, groupField: 'day',
batch: false
});

store.load(); // load the store immeditately
////
// centralized listening of DataProxy events "beforewrite", "write" and "writeexception"
// upon Ext.data.DataProxy class. This is handy for centralizing user-feedback messaging into one place rather than
// attaching listenrs to EACH Store.
//
// Listen to all DataProxy beforewrite events
//
Ext.data.DataProxy.addListener('beforewrite', function (thisProxy, action, rs) {

if (thisProxy == proxy) {
thisProxy.setApi({
read: {
url: '/entries.xml',
method: 'GET'
}, create: {
url: '/entries.xml',
method: 'POST'
}, update: {
url: '/entries/' + rs.id + '.xml',
method: 'PUT'
}, destroy: {
url: '/entries/' + rs.id + '.xml',
method: 'DELETE'
}
});
if (rs.modified)
{
if (!(rs.modified['job-id']||rs.modified['time']||rs.modified['date']||rs.modified['submitted'] ) )
{
return false;
}
}

}





// App.setAlert(App.STATUS_NOTICE, "Before " + action);
});

////
// all write events
//
Ext.data.DataProxy.addListener('write', function (proxy, action, result, res, rs) {
App.setAlert(true, action);
});

////
// all exception events
//
Ext.data.DataProxy.addListener('exception', function (proxy, type, action, options, res) {
App.setAlert(false, res.responseText);


});

function formatDate(value) {
return value ? value.dateFormat('M d, Y') : '';
}


var summary = new Ext.ux.grid.GroupSummary();

var jobsProxy = new Ext.data.HttpProxy({
api: {
read: {
url: '/jobs.xml',
method: 'GET'
}
}
});
var jobsReader = new Ext.data.XmlReader({
record: 'job',
id: 'id'
}, [{
name: 'id',
type: 'integer'
}, {
name: 'name',
type: 'string'
}, {
name: 'project_id',
type: 'integer'
}]);





var clientsProxy = new Ext.data.HttpProxy({
api: {
read: {
url: '/clients.xml',
method: 'GET'
}
}
});
var clientsReader = new Ext.data.XmlReader({
record: 'client',
id: 'id'
}, [{
name: 'id',
type: 'integer'
}, {
name: 'name',
type: 'string'
}]);



var projectsProxy = new Ext.data.HttpProxy({
api: {
read: {
url: '/projects.xml',
method: 'GET'
}
}
});
var projectsReader = new Ext.data.XmlReader({
record: 'project',
id: 'id'
}, [{
name: 'id',
type: 'integer'
}, {
name: 'name',
type: 'string'
}, {
name: 'client_id',
type: 'ineger'
}]);







var clientsCombo = new Ext.form.ComboBox({
store: new Ext.data.Store({
proxy: clientsProxy,
reader: clientsReader
}),
mode: 'local',
typeAhead: true,
typeAheadDelay: 10,
triggerAction: 'all',
width:135,
lazyRender: true,
forceSelection:true,
valueNotFoundText: 'Select a Client...',
valueField: 'id',
displayField: 'name'
});
clientsCombo.store.load();

var projectsCombo = new Ext.form.ComboBox({
store: new Ext.data.Store({
proxy: projectsProxy,
reader: projectsReader
}),
mode: 'local',
typeAhead: true,
typeAheadDelay: 10,
triggerAction: 'all',
width:135,
lazyRender: true,
valueNotFoundText: 'Select a Project...',
valueField: 'id',
displayField: 'name'
});
projectsCombo.store.load();
var jobsCombo = new Ext.form.ComboBox({
store: new Ext.data.Store({
proxy: jobsProxy,
reader: jobsReader
}),
mode: 'local',
typeAhead: true,
typeAheadDelay: 10,
triggerAction: 'all',
width:135,
lazyRender: true,
valueNotFoundText: 'Select a Job...',
valueField: 'id',
displayField: 'name'
});
jobsCombo.store.load();
clientsCombo.on('change', function(field , newVal , oldVal) {
//console.group('clientsCombo on change')
//console.dir( field, newVal,oldVal)
//console.groupEnd();
if (!field.value.match(/^Select/i))
{
projectsCombo.store.filter('client_id', field.value);
}
});
projectsCombo.on('change', function(field , newVal , oldVal) {
if (!field.value.match(/^Select/i))
{
jobsCombo.store.filter('project_id' ,field.value );
}
});


Ext.onReady(function () {


function refreshGridOnLoad(store) {
if (!store.loadCounter)
{
if (userGrid)
{
userGrid.view.refresh()
}
store.loadCounter = 0;
}
store.loadCounter++;
}


jobsCombo.store.on('load' , refreshGridOnLoad)
projectsCombo.store.on('load' , refreshGridOnLoad)
clientsCombo.store.on('load' , refreshGridOnLoad)


Ext.util.Format.comboRenderer = function (combo) {
return function (value) {
if (!value) {
return combo.valueNotFoundText;
} else {
var record = combo.findRecord(combo.valueField, value);
return record ? record.get(combo.displayField) : combo.valueNotFoundText;
}
}
};



var userColumns = [{
header: "Date",
width: 50,
dataIndex: 'day',
editor: new Ext.form.DateField({
format: 'd/m/y'
}),
renderer: formatDate,
sortable: true
},{
header: "Client",
width: 70,
dataIndex: 'client-id',
editor: clientsCombo,
renderer: Ext.util.Format.comboRenderer(clientsCombo)
}, {
header: "Project",
width: 70,
dataIndex: 'project-id',
editor: projectsCombo,
renderer: Ext.util.Format.comboRenderer(projectsCombo)
}, {
header: "Job",
width: 70,
dataIndex: 'job-id',
editor: jobsCombo,
renderer: Ext.util.Format.comboRenderer(jobsCombo)
}, {
header: "Hours",
width: 50,
dataIndex: 'time',
summaryType: 'sum',
editor: new Ext.form.NumberField({
allowBlank: false,
allowNegative: false,
maxValue: 24
}),
renderer: function (value) {



if (value) {
if (value <= 24) {
return value + " hours"
}
else if (value > 24) {
return "<div class='x-grid3-summary-err'>" + value + " hours ?</div>"
}
}
else if (!value) {
return "0 hours"
}

}
}
];





// Create a typical GridPanel with RowEditor plugin
var userGrid = new Ext.grid.EditorGridPanel({
renderTo: 'user-grid',
// iconCls: 'icon-grid',
frame: true,
title: 'Entries',
autoScroll: true,
height: 500,
store: store,
clicksToEdit: 1,
plugins: summary,
columns: userColumns,
tbar: [{
text: 'Add',
// iconCls: 'silk-add',
handler: onAdd
}, '-', {
text: 'Delete',
// iconCls: 'silk-delete',
handler: onDelete
}, '-', {
text: 'Submit To Management',
handler: onSubmitToManagement
}],
view: new Ext.grid.GroupingView({
forceFit: true,
})


});






/**
* onAdd
*/

function onAdd() {
var e = new userGrid.store.recordType({
day: (new Date()).clearTime(),
project_id: 0,
hours: 0
});
userGrid.stopEditing();
userGrid.store.insert(0, e);
userGrid.startEditing(0);
}
/**
* onDelete
*/
function onDelete() {
var index = userGrid.getSelectionModel().getSelectedCell();
if (!index) {
return false;
}

var rec = userGrid.store.getAt(index[0]);

userGrid.store.remove(rec);
}
/**
* onSubmitToManagement
* & hleper functions
*/
function onSubmitToManagement() {
Ext.Msg.confirm("Death be upon you", "you hereby forgo any right to your soul, past present and future, it will be harvested immediately, Are these terms acceptable?", areYouReallySure)
}

function areYouReallySure(btn) {
if (btn == 'yes') {
Ext.Msg.confirm("Are you sure", "cause it really hurts to have your soul ripped out....", submitEntries)
}
}

function submitEntries(btn) {
if (btn == 'yes') {
console.log("submitting")
userGrid.store.each(function submit() {
this.beginEdit()
this.set('submitted', true);
this.endEdit()
this.commit()
return true;
})
store.filterBy(function (rec, id) {
if (rec.get('submitted')) {
return false;
}
else {
return true;
}
})

userGrid.view.refresh()

}
}

userGrid.on('afteredit', function (e) {
e.grid.view.refresh()


})
});

johnholden
29 Nov 2010, 5:42 PM
I have the exact same problem. Does anyone know of a solution, or better way to handle this situation in general?

Condor
30 Nov 2010, 12:12 AM
The correct way to handle this is to filter (or load) the combobox store in the beforeedit event of the grid cell.

Cannon
30 Nov 2010, 1:56 PM
would have been nice to have reply that fast when i posted...
i want to say i tried that and id didnt work for some reason but i ended up loading a store on focus event, with url parameters to filter it based on previousSibling's value, then load the full list again on blur event.

so for each row you edit you send like 6 ajax requests ( with 3 combos ) but it works for me


jobs belong to projects which belong to clients,

select a client, get a list of its projects, get a list of its jobs



projectsCombo.on('focus' , function(field){
var client = field.previousSibling().value; // get last fields value
if (client ){
if (!client.match(/^Select/i)){ \\ if the value isnt the default 'select a ___' text in the combo
projectsCombo.store.load({params:'client_id=' + client} ) // load a filtered store
}
}
}
)
jobsCombo.on('focus' , function(field){
var project = field.previousSibling().value;
if (project){
if (!project.match(/^Select/i)){
jobsCombo.store.load( { params: 'project_id=' + project } )
}
}
}
)
projectsCombo.on('blur' , function(field){
projectsCombo.store.load({params:''} ) //reutrn to global list
}
)
jobsCombo.on('blur' , function(field){
jobsCombo.store.load( { params: '' } )
}
)