js code
Last but not least is the js code. As mentioned, this is probably overly commented. It's basically a compilation of what I've picked up from the various tutorials, screencasts, manual, api, forums, etc.
I've tested on FF2.0.0.8 and IE7 and it appears to work ok.
/** * Editor Grid * Interacts with server side php/MySQL * November 18, 2007 * Michael LeComte */
/** * Ext JS Library 2.0 RC 1 * Copyright(c) 2006-2007, Ext JS, LLC. * licensing@extjs.com * http://extjs.com/license */
/** * Three main things to do: * 1.0 Setup the Data Source (setupDataSource) * 2.0 Create the Column Model (getColumnModel) * 3.0 Build the Grid (buildGrid) */
// Reference local blank image to prevent link back to extjs.com Ext.BLANK_IMAGE_URL = '../../resources/images/default/s.gif'; //-> this = window
// Create a namespace Object /* By specifying our own namespace we encapsulate all variables and methods in one global object in order to avoid any conflicts or changes when working with other javascript files. This pattern scales well as the project becomes more complex and as its API grows. It stays out of the global namespace, provides publicly addressable API methods, and supports protected or “private” data and methods along the way.*/ Ext.namespace('myNameSpace'); //define namespace with some 'name' /* This assigns an empty object myNameSpace as a member of the Ext object (but doesn’t overwrite myNameSpace if it already exists).*/
//////////////////////////////////////////////////////////////////////////////////////// // Now we can begin adding members to the Ext.myNameSpace application using the Module Pattern myNameSpace.myModule = function(){//creates a property 'myModule' of the namespace object
//-> this = window /******************/ /*--Private Area--*/ // can only access this area via the Public Area /*--Private Variables--*/ // Not accessible directly from outside of the module // These lines executed before document fully loaded, so don't access DOM // from here (elements do not exist yet) var myPrivateVar = "I can be accessed only from within Ext.myNameSpace.myModule."; var ds; var dsIndustry; var grid; //component var colModel; // definition of the columns var gridForm; var myRecordObj; var myReader; var primaryKey='companyID'; /*--Private Functions--*/ // Not accessible directly from outside of the module var myPrivateMethod = function () { Ext.log("I can be accessed only from within Ext.myNameSpace.myModule"); }
////////////////////////////////////////////////////////////////////////////////////////
/** * 1.0 Setup the Data Source (setupDataSource) * 1.1 Create Data Record * 1.2 Define Reader * 1.3 Create Data Store(s) * 1.4 Load Data Store(s) */ function setupDataSource() {
/*1.1 Create data Record This creates a constructor for a specific record layout defining the fields that make up an individual row of data. The Reader is what links the Data Object (server side source or other static variable) with the DataStore. The data Record tells the Reader how to associate or map the data from the Data Object with the Data Store. You can create the data record inline during creation of the reader, but creating the object separately here offers us the ability to add records dynamically, as an example see the addRecord() function below. To create a data record, we pass in an array of field definition objects specifying how to reference the data in your Data Object (the source of your data, ie. a database or static variable 'myData') */ myRecordObj = Ext.data.Record.create([ {name: 'company', mapping: 'company', sortDir: 'ASC', sortType: 'asUCString'}, /* name: 'name' by which the field is referenced within the Record. * It will match the columnModel "dataIndex" property * mapping: required only if the 'reference' != mapping * where reference is the key in the source data file * eg. db field name, xml tag name, etc. * sortDir: initial sort direction * sortType: defines explicitly how to perform sort, see Ext.data.SortTypes */ {name: 'price', type: 'float'}, //type = how the data should be displayed {name: 'tax', type: 'float'}, //type = how the data should be displayed {name: 'change', type: 'float'}, //will use mapping = 'change' {name: 'pctChange', type: 'float'}, //in the following line dateFormat must match that from the data source (see 'Class Date' in API) {name: 'lastChange', type: 'date', dateFormat: 'Y-m-d H:i:s'}, {name: 'industryID'}, {name: 'risk'}, {name: 'stars'}, {name: 'check'}, {name: primaryKey}//Note the order of fields defined here does not matter, it doesn't relate //to the order of the fields in your source, nor the order displayed in the grid //You also do not have to include every field in your data source here //So if your database table has 10 columns, you can specify 2 here if you want ]);
/*1.2. Define Reader The reader extracts the field's value from the data object creating an Array of Ext.data.Record objects. We need to specify where the reader should look in the response for the data, aka the 'root'. We can also optionally tell the reader where to look for the total number of records the Data Object has in case we don't pass all of the data in one go (pagination, etc.). The Data Object can be in several different base formats, so we need to use the proper reader that knows how to deal with the data format, so we specify the reader based on the format of the data returned (Xml, Json, Array) */ myReader = new Ext.data.JsonReader({ //creates array from JSON response /* var myReader = new Ext.data.ArrayReader({//creates array from static array var myReader = new Ext.data.XmlReader({ //creates array from XML response */ //1st parameter for reader constructor is to specify config options for reader: root: 'results', // name of the property that is container for an Array of row objects totalProperty: 'total', /*name of the property from which to retrieve the total number of records in the dataset. This is only needed if the whole dataset is not passed in one go, but is being paged from the remote server.*/ /* The response should come back like this (using Firebug, click on 'Net' tab, click line corresponding to serverside script, then click "Response" to view the response; can also get same information using the "Console" tab): ({"total":"29", <----the 'totalProperty' "results":[ <----the 'root' {"0":"Alcoa Inc","company":"Alcoa Inc", "1":"29.01","price":"29.01", "2":"0.42","change":"0.42", "3":"1.47","pctChange":"1.47", "4":"2007-10-02 00:00:00","lastChange":"2007-10-02 00:00:00", "5":"143","id":"143"}, {next record}, {next record}]}) */ //record: 'item' //the XML delimiter tag for each record (row of data) id: primaryKey //the property within each row object that provides an ID for the record (optional) }, //2nd parameter for reader constructor is record constructor object //that specifies the record definition (the recordType object containing the field mapping) myRecordObj //instead of defining inline just pass a reference to the object );
/** * 1.3. Create Data Store(s) * Set up the data Store object * A 'Store' is a client side cache of Ext.data.Record objects which * provide input data for widgets. * Basically you create this representation of your server side data * by defining the objects which specify: * 1. proxy - * how to the read the raw data (which proxy type) * where to read data (the url to the data source) * method to read data (GET or POST request) * 2. reader - takes data and breaks into columns */
// Data Store #1 ds = new Ext.data.GroupingStore({ //if grouping //ds = new Ext.data.Store({ //if not grouping /*1. specify how and where to access data. If data is specified within this file this argument is null Otherwise there are a few options for pulling the data you'd use: a. HttpProxy reads data from the same domain/server b. ScriptTagProxy reads data object from different domain/server c. MemoryProxy passes data specified in constructor */ //set the proxy (method to get the raw data) - required for ScriptTagProxy //note if using HttpProxy you can just specify the url property (HttpProxy is created): proxy: new Ext.data.HttpProxy({ //where to retrieve data url: 'grid-editor-mysql-php.php', //url to data object (server side script) method: 'POST' }), baseParams:{task: "readStock"},//this parameter is passed for any HTTP request /*2. specify the reader The Store object doesn't know what the data looks like or or what format it is in, so a 'reader' is used to read the data into Records The reader object defined here will process the data object and return an array of Ext.data.Record objects which are cached and keyed per their id property mapping the data we need*/ reader: myReader, sortInfo:{field: 'company', direction: "ASC"} //remoteSort: true,//true if sort from server (false = sort from cache only) //groupField:'industry', //added for GroupingStore, specifies initial group sort });
// Data Store #2 // This store will hold the data for the dropdown options dsIndustry = new Ext.data.Store({ proxy: new Ext.data.HttpProxy({ //where to retrieve data url: 'grid-editor-mysql-php.php', //url to data object (server side script) method: 'POST' }), baseParams:{task: "readIndustry"},//this parameter is passed for any HTTP request /*2. specify the reader*/ reader: new Ext.data.JsonReader( { root: 'results',//name of the property that is container for an Array of row objects id: 'industryID'//the property within each row object that provides an ID for the record (optional) }, [ {name: 'industryID'},//name of the field in the stock table (not the industry table) {name: 'industryName'} ] ), sortInfo:{field: 'industryName', direction: "ASC"} } );//end dsIndustry
/*1.4 Load Data Store(s) Use ds.loadData to load data directly from static data block array 'myData' ds.loadData(myData); Use ds.load to load data from dynamic data source*/ dsIndustry.load(); //load data store holding our data for the industry column dropdown options //note originally I had this store load second. But when I did that //the grid was apparently getting rendered the first time before the dsIndustry //store was loaded so the rendered wasn't working (because the 'poop' portion //of the if statement below was invoked since the store didn't exist yet //After playing around I've noticed that the store must get returned before //the execution gets to the grid render() line, otherwise it will still render //the grid but the store will be empty and thus display an empty grid. If you //use the paging toolbar refresh that will probably show all the data because //it just refreshes the page from the cached data store ds.load({params: { //parameters for the FIRST page load, use baseParams above for ALL pages. start: 0, //pass start/limit parameters for paging limit: myNameSpace.myModule.perPage// }}); //it is at this point that we'll request the data from the server. //note that this file keeps going independent of the server, thus //making this an asynchronous process, hopefully the server will respond //prior to calling render() on the grid, otherwise we'll have a blank grid. //If you're stepping through firebug, it's at this point you'll see the //XHR get sent out.
} // end setupDataSource ////////////////////////////////////////////////////////////////////////////////////////
/** * 2.0 Get the Column Model (getColumnModel) * We have all of this data that came from the source (possibly server side) and then * we read that data into a client side cache (store). Now we have to decide what * parts of that data we want to display and how we want to display it. We may have * some data we don't want displayed, or want some of it shown in a particular way. * 2.1 Create Custom Renderers * 2.2 Create the Column Model */ function getColumnModel() { if(!colModel) { //only need to create columnModel if it doesn't already exist
/*2.1. Create Custom Renderers*/
/** * Italic Custom renderer function * val rendered in italics * @param {Object} val */ function italic(val){ return '<i>' + val + '</i>'; };
/** * Red/Green Custom renderer function * renders red if <0 otherwise renders green * @param {Object} val */ function renderPosNeg(val){ if(val >= 0){ //-> this = obj (row from grid, properties of id, name='change', style) return '<span style="color:green;">' + val + '</span>'; }else if(val < 0){ return '<span style="color:red;">' + val + '</span>'; } return val; };
/** * Percent Custom renderer function * Renders red or green with % * @param {Object} val */ function renderPctChange(val){ if(val >= 0){ //-> this = obj (row from grid, properties of id, name='pctChange', style) return '<span style="color:green;">' + val + '%</span>'; }else if(val < 0){ return '<span style="color:red;">' + val + '%</span>'; } return val; };
/** * Risk Custom renderer function * Renders according to risk level * @param {Object} val */ function renderRisk(data, cell, record, rowIndex, columnIndex, store){ switch(data) { case "high": cell.css = "redcell"; return "high"; case "medium": return "medium"; case "low": return "low"; } };
/** * Star Custom renderer function * Renders a picture according to value * @param {Object} val */ function renderStars(data, cell, record, rowIndex, columnIndex, store){ switch(data) { case "1": cell.css = "stars1"; return 1;//returns text over the background image case "2": cell.css = "stars2"; return;//just shows the background image case "3": cell.css = "stars3"; return; case "4": cell.css = "stars4"; return; case "5": cell.css = "stars5"; return; } };
/** * Date renderer function * Renders a date * @param {Object} val */ function renderDate(value){ //Ext.util.Format.dateRenderer('m/d/Y') return value ? value.dateFormat('M d, Y') : ''; };
// custom column plugin example //var checkColumn = new Ext.grid.CheckColumn({ this.checkColumn = new Ext.grid.CheckColumn({ header: "Check", dataIndex: 'check', width: 9, sortable: true }); /** * Check Column event listener * @param {Object} element * @param {Object} e * @param {Object} record */ this.checkColumn.on('click', function(element, e, record) { //alert(record.get('check')); var myField = this.dataIndex;//the field name var check = record.data[this.dataIndex];//same as record.data.check (but more abstract) var checkStatus = check ? 'checked' : 'unchecked'; var checkItem = record.data.company; var checkID = record.data.companyID; Ext.example.msg('Item Check', 'You {0} the "{1}" check box, ID = {2}.', checkStatus, checkItem, checkID); var myMsg = 'You <b>'+ checkStatus + '</b> the "<i>' + checkItem + '</i>" check box, ID = ' + '<span style="color:blue;">' + checkID + '</span>.'; Ext.MessageBox.alert('Item Check', myMsg); var checkBoolean = check ? 1 : 0;
//update the database Ext.Ajax.request( { //ajax request configuration waitMsg: 'Saving changes...', url: 'grid-editor-mysql-php.php', //url to server side script params: { //these will be available via $_POST or $_REQUEST: task: "update", //pass task to do to the server script key: primaryKey,//pass to server same 'id' that the reader used keyID: checkID,//for existing records this is the unique id (we need this one to relate to the db) //newRecord: isNewRecord,//pass the new Record status indicator to server for special handling field: myField,//the column name value: checkBoolean,//the updated value originalValue: !checkBoolean//the original value (oGrid_Event.orginalValue does not work for some reason) },//end params failure:function(response,options){ Ext.MessageBox.alert('Warning','Oops...'); },//end failure block success:function(response,options){ //Ext.MessageBox.alert('Success','Yeah...'); if(checkID == 0){ var responseData = Ext.util.JSON.decode(response.responseText);//passed back from server var newID = responseData.newID;//extract the ID provided by the server record.set('newRecord','no');//reset the indicator since update succeeded record.set('companyID',newID);//assign the id to the record ds.commitChanges();//commit changes (removes the red triangle which indicates a 'dirty' field) } else { ds.commitChanges();//commit changes (removes the red triangle which indicates a 'dirty' field) } }//end success block }//end ajax request config ); //end ajax request });
/** * 2.2. Create the Column Model * Set up the ColumnModel object to define the initial layout/display of the grid. * The order specified here defines the order of initial column display. * We also define how each column in the grid correlates (maps) to our * DataStore with dataIndex. * "mapping" specifies how the data object relates/maps to the DataStore (client side cache of data). * "dataIndex" specifies how the DataStore relates/maps to the ColumnModel (actual display). * It can be extended to provide custom or reusable ColumnModels */ colModel = new Ext.grid.ColumnModel([ //instantiate ColumnModel //Here we give comma separated definitions of the fields we want //displayable (some may be initially hidden) in the grid. //Note you need not display every column in your data store here; //you can include fields here and have them be hidden or //you can just not include some fields in your grid whatsoever (maybe //you just retrieved them to do other behind the scenes processing //client side instead of server side) { id: 'classCompanyID',//by placing an id on this column we can later reference the column specifically //For instance we could set a css style to highlight the column (.x-grid3-col-classCompanyID) //This doesn't work well with an editor grid though, as the red triangle gets covered up. //Perhaps something with the z-index could be changed so the red triangle remained on top? //Another use might be to select an entire column and do something with it //Example anyone? header:"ID",//header = text that appears at top of column width: 9, //column width sortable: true,//false (default) to disable sorting by this column locked: false, align: 'right',//default is to align to the left //hidden: true, //true to initially hide the column dataIndex: 'companyID'//the DataStore field "name" this column draws its data from //dataIndex = rt name },{ id: 'classCompany',//by placing an id on this column we can later reference the column specifically //currently the html page sets a css style to highlight the column (.x-grid3-col-classCompany) //we may be able to select an entire column and do something with it //example anyone? header:"Company",//header = text that appears at top of column width: 40, //column width sortable: true,//false (default) to disable sorting by this column locked: false, //resizable: false, //disable column resizing (can also used fixed = true) dataIndex: 'company',//the DataStore field "name" this column draws its data from //dataIndex = rt name editor: new Ext.form.TextField({ //TextField editor - for an editable field add an editor //specify options allowBlank: false //default is true (nothing entered) }) },{ header: "Price", width: 12, sortable: true, //hidden: true,//true to initially hide the column //(NOTE: as of Ext2.0-rc1 the GroupingStore has problems sizing the grid when columns are hidden) renderer: Ext.util.Format.usMoney,//optional rendering function to provide customized data formatting dataIndex: 'price', align: 'right',//default is to align to the left editor: new Ext.form.NumberField({ //specify options allowBlank: false, //default is true (nothing entered) allowNegative: false, //could also use minValue maxValue: 100 }) },{ header: "Tax", width: 12, sortable: true, renderer: Ext.util.Format.usMoney,//optional rendering function to provide customized data formatting dataIndex: 'tax', align: 'right'//default is to align to the left },{ header: "Change", width: 12, sortable: true, renderer: renderPosNeg, dataIndex: 'change', align: 'center' }, {header: "% Change", width: 15, sortable: true, renderer: renderPctChange, dataIndex: 'pctChange'}, { header: "Last Updated", width: 20, sortable: true, renderer: renderDate, dataIndex: 'lastChange', editor: new Ext.form.DateField({ //DateField editor //specify options allowBlank: false, //default is true (nothing entered) //format: 'd/m/y', //defaults to 'm/d/y', if there is a renderer //specified it will render whatever this form //returns according to the renderer minValue: '10/15/07', //anything prior to is greyed out/unclicklable //validator prevents typing a new date violating criteria disabledDays: [0, 3, 6], disabledDaysText: 'Closed on this day' }) },{ header: "Industry", width: 23, sortable: true, dataIndex: 'industryID',//this is what is specified in the reader editor: new Ext.form.ComboBox({ //dropdown based on server side data (from db) typeAhead: false, //will be querying database so may not want typeahead consuming resources triggerAction: 'all', lazyRender: true,//prevents combo box from rendering until requested, should always be true for editor store: ds,//Industry,//where to get the data for our combobox displayField: 'industryName',//the underlying data field name to bind to this ComboBox //(defaults to undefined if mode = 'remote' or 'text' if transforming a select) valueField: 'industryID' //the underlying value field name to bind to this ComboBox }), renderer: function(data) { record = dsIndustry.getById(data);//same as accessing this.data.key(id) if(record) { return record.data.industryName; } else { //return data; return 'poop'; } } },{ header: "Risk", width: 11, sortable: true, renderer: renderRisk, dataIndex: 'risk', editor: new Ext.form.ComboBox({ //dropdown based on client side data (from html) typeAhead: true, triggerAction: 'all', transform:'riskID',//look for this id to transform the html option values to a dropdown lazyRender:true,//prevents combo box from rendering until requested, should always be true for editor listClass: 'x-combo-list-small' //css class to apply to the dropdown list element }) },{ header: "Stars", width: 8, sortable: true, renderer: renderStars, dataIndex: 'stars', align: 'center' }, checkColumn //defined above ]);//end colModel //instead of specifying sorting permission by individual columns can also specify for entire grid //colModel.defaultSortable = false;
}//end if colModel return colModel; }//getColumnModel
////////////////////////////////////////////////////////////////////////////////////////
/** * 3.0 Build the Grid (buildGrid) * 3.1 Create Handlers * 3.2 Create (instantiate) the Grid * 3.3 Render the Grid (make it lazy if you want) * 3.4 Add listeners to the Grid */ function buildGrid() { /* gridForm = new Ext.BasicForm( Ext.get("updategrid"), { } ); //end gridForm */
/** * 3.1. Create Handlers * Create functions to handle various events */
/** * Function for Refreshing Grid */ function refreshGrid() { ds.reload();// }; // end refresh
/** * Handler for Adding a Record */ function addRecord() { var r = new myRecordObj({ //specify default values companyID: 0,//use this to trigger special handling when updating company: '', //you can't comment out this line if you want the editor //to start there, as it will show the html tags price: 0.00, tax: 0.00, change: 0.00, pctChange: 0.00, lastChange: (new Date()).clearTime(), industry: '', risk: '', stars: '0', //newRecord:'yes',//use this to trigger special handling when updating //id: 0//this is not helpful, }); grid.stopEditing();//stops any acitve editing ds.insert(0, r); //1st arg is index, //2nd arg is Ext.data.Record[] records //very similar to ds.add, with ds.insert we can specify the insertion point grid.startEditing(0, 1);//starts editing the specified rowIndex, colIndex //make sure you pick an editable location in the line above //otherwise it won't initiate the editor }; // end addRecord
//add an event to handle any updates to grid /** * Handler to control grid editing * @param {Object} oGrid_Event */ function handleEdit(editEvent) { var gridField = editEvent.field;//determine what column is being edited updateDB(editEvent);//start the process to update the db with cell contents //I don't want to wait for server update to update the Total Column if (gridField == 'price'){ getTax(editEvent);//start the process to update the Tax Field } } /** * Function for updating database * @param {Object} oGrid_Event */ function updateDB(oGrid_Event) { /*Do we need to disable a new record from further editing while the first request is being made since the record may not have the new companyID in time to use to properly handle other updates of the same record? Dates come through as an object instead of a string or numerical value, so do a check to prep the new value for transfer to the server side script*/ if (oGrid_Event.value instanceof Date) { //format the value for easy insertion into MySQL var fieldValue = oGrid_Event.value.format('Y-m-d H:i:s'); } else { var fieldValue = oGrid_Event.value; } //submit to server Ext.Ajax.request( //alternative to Ext.form.FormPanel? or Ext.BasicForm { //specify options (note success/failure below that receives these same options) waitMsg: 'Saving changes...', //url where to send request url: 'grid-editor-mysql-php.php', //url to server side script //method: 'POST', //if specify params default is 'POST' instead of 'GET' params: { //these will be available via $_POST or $_REQUEST: task: "update", //pass task to do to the server script key: primaryKey,//pass to server same 'id' that the reader used keyID: oGrid_Event.record.data.companyID,//for existing records this is the unique id (we need this one to relate to the db) //we'll check this server side to see if it is a new record // bogusID: oGrid_Event.record.id,//for new records Ext creates a number here unrelated to the database // newRecord: isNewRecord,//pass the new Record status indicator to server for special handling field: oGrid_Event.field,//the column name value: fieldValue,//the updated value originalValue: oGrid_Event.record.modified//the original value (oGrid_Event.orginalValue does not work for some reason) //this might(?) be a way to 'undo' changes other than by cookie? //when the response comes back from the server can we make an undo array? },//end params //the function to be called upon failure of the request (404 error etc, NOT success=false) failure:function(response,options){ Ext.MessageBox.alert('Warning','Oops...'); //ds.rejectChanges();//undo any changes },//end failure block success:function(response,options){ //Ext.MessageBox.alert('Success','Yeah...'); if(oGrid_Event.record.data.companyID == 0){ var responseData = Ext.util.JSON.decode(response.responseText);//passed back from server var newID = responseData.newID;//extract the ID provided by the server //oGrid_Event.record.id = newID; oGrid_Event.record.set('newRecord','no');//reset the indicator since update succeeded oGrid_Event.record.set('companyID',newID);//assign the id to the record //note the set() calls do not trigger everything since you may need to update multiple fields for example //so you still need to call commitChanges() to start the event flow to fire things like refreshRow() ds.commitChanges();//commit changes (removes the red triangle which indicates a 'dirty' field) //var whatIsTheID = oGrid_Event.record.modified; } else { ds.commitChanges();//commit changes (removes the red triangle which indicates a 'dirty' field) } }//end success block }//end request config ); //end request }; //end updateDB
/** * Function for updating Tax shown in grid * @param {Object} oGrid_Event */ function getTax(oGrid_Event) { //submit to server Ext.Ajax.request( //alternative to Ext.form.FormPanel? or Ext.BasicForm { //specify options (note success/failure below that receives these same options) //waitMsg: 'Saving changes...', //url where to send request url: 'grid-editor-mysql-php.php', //url to server side script //method: 'POST', //if specify params default is 'POST' instead of 'GET' params: { //these will be available via $_POST or $_REQUEST: task: "calcTax", //pass task to do to the server script price: oGrid_Event.value//the updated value },//end params //the function to be called upon failure of the request failure:function(response,options){ Ext.MessageBox.alert('Warning','Oops...'); //ds.rejectChanges();//undo any changes },//end failure block success:function(response,options){ //Ext.MessageBox.alert('Success','Yeah...'); var responseData = Ext.util.JSON.decode(response.responseText);//passed back from server var myTax = responseData.tax;//extract the value provided by the server //oGrid_Event.record.data.tax = myTax;//assign the tax to the record //oGrid_Event.record.tax= myTax;//assign the id to the record oGrid_Event.record.set('tax',myTax); ds.commitChanges();//commit changes (removes the red triangle which indicates a 'dirty' field) }//end success block }//end request config ); //end request }; //end getTax
/** * Handler for Deleting record(s) */ function handleDelete() { var selectedKeys = grid.selModel.selections.keys; //returns array of selected rows ids only if(selectedKeys.length > 0) { Ext.MessageBox.confirm('Message','Do you really want to delete selection?', deleteRecord); } else { Ext.MessageBox.alert('Message','Please select at least one item to delete'); }//end if/else block }; // end handleDelete
/** * Function for Deleting record(s) * @param {Object} btn */ function deleteRecord(btn) { if(btn=='yes') { /* block if just want to remove 1 row var selectedRow = grid.getSelectionModel().getSelected();//returns record object for the most recently selected //row that is in data store for grid if(selectedRow){ ds.remove(selectedRow); } //end of block to remove 1 row */ var selectedRows = grid.selModel.selections.items;//returns record objects for selected rows (all info for row) var selectedKeys = grid.selModel.selections.keys; //returns array of selected rows ids only
//note we already did an if(selectedKeys) to get here
var encoded_keys = Ext.encode(selectedKeys);//encode array into json //submit to server Ext.Ajax.request( //alternative to Ext.form.FormPanel? or Ext.BasicForm.submit { //specify options (note success/failure below that receives these same options) waitMsg: 'Saving changes...', //url where to send request url: 'grid-editor-mysql-php.php', //url to server side script params: { //these will be available via $_POST or $_REQUEST: task: "delete", //pass task to do to the server script companyID: encoded_keys,//the unique id(s) key: primaryKey//pass to server same 'id' that the reader used }, /* you can also specify a callback (instead of or in addition to success/failure) for custom handling. If you have success/failure defined, those will fire before 'callback'. This callback will fire regardless of success or failure.*/ callback: function (options, success, response) { if (success) { //success will be true if the request succeeded Ext.MessageBox.alert('OK',response.responseText);//you won't see this alert if the next one pops up fast var json = Ext.util.JSON.decode(response.responseText); Ext.MessageBox.alert('OK',json.del_count + ' record(s) deleted.');//need to move this to an after //event because it will fire before the grid is re-rendered (while the deleted row(s) are still there //You could update an element on your page with the result //from the server (e.g.<div id='total'></div>) //var total = Ext.get('total'); //total.update(json.sum); } else { Ext.MessageBox.alert('Sorry, please try again. [Q304]',response.responseText); } }, /* */ //the function to be called upon failure of the request (server script, 404, or 403 errors) failure:function(response,options){ Ext.MessageBox.alert('Warning','Oops...'); //ds.rejectChanges();//undo any changes }, success:function(response,options){ //Ext.MessageBox.alert('Success','Yeah...'); ds.reload();//commit changes and remove the red triangle which indicates a 'dirty' field } } //end Ajax request config );// end Ajax request initialization };//end if click 'yes' on button }; // end deleteRecord
/** * 3.2. Create (instantiate) the Grid * This creates the actual GUI for the Grid. * We specify here where, how, and when to render the Grid. */ //grid = new Ext.grid.GridPanel({ //to instantiate normal grid grid = new Ext.grid.EditorGridPanel({ //to instantiate editor grid //el:'grid-example', //html element (id of the div) where the grid will be rendered //'renderTo' does the same as 'el', except eliminated the need to explicitly call render() //renderTo: 'grid-example',//could also render it directly to document.body iconCls: 'icon-grid',//we create our own css with a class called 'icon-grid' store: ds, //the DataStore object to use (ds: is shorthand) colModel: getColumnModel(), //gets the ColumnModel object to use (cm: is shorthand) autoExpandColumn: 'company', //which column to stretch in width to fill up the grid width and not leave blank space //autoSizeColumns: true,//deprecated as of Ext2.0 //Enable a Selection Model. The Selection Model defines the selection behavior, //(single vs. multiple select, row or cell selection, etc.) selModel: new Ext.grid.RowSelectionModel({singleSelect:false}),//true to limit row selection to 1 row}) //footer: true, height:350,//you must specify height or autoHeight //autoHeight:true,//autoHeight resizes the height to show all records width:740, title:'This is the Grid Title', clicksToEdit:2,//number of clicks to activate cell editor, default = 2 plugins:this.checkColumn, //frame:true,//add a frame around the grid; defaults to no frame cellclick: function() { var record = grid.getStore().getAt(rowIndex); //get the record var fieldName = grid.getColumnModel().getDataIndex(columnIndex);//get field name var data = record.get(fieldName); Ext.MessageBox.alert('title','inside function'); //title: 'My Title Here', //msg: 'outside the function...' //fn: myCallBackFunction,//the callback function after the msg box is closed //scope:this//scope of callback function }, stripeRows: true,//applies css classname to alternate rows, defaults to false //trackMouseOver: true,//highligts rows on mousever //Add a bottom bar bbar: new Ext.PagingToolbar({ pageSize: myNameSpace.myModule.perPage,//default is 20 store: ds, displayInfo: true,//default is false (to not show displayMsg) displayMsg: 'Displaying topics {0} - {1} of {2}', emptyMsg: "No data to display",//display message when no records found items:[ '-', { pressed: true, enableToggle:true, text: 'Show Preview', cls: 'x-btn-text-icon details' //toggleHandler: toggleDetails }] }), //Add a top bar tbar: [ { text: 'Add Record', tooltip: 'Click to Add a row', iconCls:'add', //we create our own css with a class called 'add' //custom class not included in ext-all.css by default handler: addRecord //what happens when user clicks on it }, '-', //add a separator { text: 'Delete Selected', tooltip: 'Click to Delete selected row(s)', handler: handleDelete, //what happens when user clicks on it iconCls:'remove' //we create our own css with a class called 'add' }, '-', //add a separator { text: 'Refresh', tooltip: 'Click to Refresh the table', handler: refreshGrid, //what happens when user clicks on it iconCls:'refresh' //we create our own css with a class called 'add' } ], //this is the key to showing the GroupingStore view: new Ext.grid.GroupingView({ forceFit:true, //custom grouping text template to display the number of items per group groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})' }) /**/ });//end grid // ## THIS DOES NOT WORK ## // /* var rz = new Ext.Resizable("grid-example",{ wrap:true, minHeight:100, pinned: true, handles:'all' }); rz.on("resize", grid.autosize, grid); */
/** * 3.3 Render the Grid (make it lazy if you want) * Explicitly rendering the grid is only required if "renderTo" is not specified * in the configuration of the grid object above. * In Ext2.0, every component automatically supports "lazy" (on demand) rendering. * The rendering pipeline is managed automatically if you use "renderTo" in the * grid constructor. Instead of using "renderTo" you can explicitly render the grid * when you want using render(). This gives you the flexibility or power to control * the rendering process. In addition to render() you can also use the beforerender() * event (see Ext.Component). * 3.4 Add listeners to the Grid */ grid.render('grid-example');//1st argument is the container, 2nd argument is the position within //the div (defaults to end of the container) /** * 3.4 Add listeners to the Grid */
/** * Add right click event * rowcontextmenu fires when a row is right clicked */ grid.addListener('rowcontextmenu', onMessageContextMenu);
//callback function for the right click event function onMessageContextMenu(grid, rowIndex, e) { e.stopEvent(); var coords = e.getXY(); var record = grid.getStore().getAt(rowIndex);//OK, we have our record, now how do we pass //it to the referenced handler?
var messageContextMenu = new Ext.menu.Menu({ id: 'messageContextMenu', items: [ { text: 'Properties', handler: onMessageContextItemClick //handler: showRecord(record) }, { text: 'I like Ext', checked: true, // when checked has a boolean value, it is assumed to be a CheckItem checkHandler: onItemCheck } ] }); //predefine a menu item var menuItem = new Ext.menu.Item({text: '<i>New Item</i>'});
//shows how to add items dynamically var item = messageContextMenu.add( '-', menuItem, //add item by reference { text: '<u>View in new tab</u>', //handler: onMessageContextItemClick(this,['open']), handler: function(){ this.viewer.openTab(this.ctxRecord); }, iconCls:'add' }, {text: '<b>Print</b>', menu: new Ext.menu.Menu({// <-- submenu by nested config object items: [ {text: 'PDF', handler:function(){callPrintPreview("PDF");} }, {text: 'EXCEL',handler:function(){callPrintPreview("EXCEL ");} }, {text: 'HTML', handler:function(){callPrintPreview("HTML") ;} }, {text: 'WORD', handler:function(){callPrintPreview("WORD") ;} } ] })}, {text: '<b>Save Preferences</b>',handler: function(){saveUserPref(Ext.encode(colModel.config));}} );
messageContextMenu.showAt([coords[0], coords[1]]); e.preventDefault();//to disable the standard browser context menu }
/** * Handlers for right clicks */ function onMessageContextItemClick(item) { //Ext.MessageBox.alert('Request','You selected the {0} menu item',item.text); Ext.MessageBox.alert('Request','You selected '+this.text); }; // end right click handler
function onItemCheck(item, checked){ Ext.example.msg('Item Check', 'You {1} the "{0}" menu item.', item.text, checked ? 'checked' : 'unchecked'); }
function SaveToExcel(item) { Ext.MessageBox.alert('Request','You selected '+this.text); }; function callDelete(item) { Ext.MessageBox.alert('Request','You selected '+this.text); }; function callTrade(item) { Ext.MessageBox.alert('Request','You selected '+this.text); }; function callPrintPreview(item) { Ext.MessageBox.alert('Request','You selected '+this.text); };
/** * Add an event to handle any updates to grid */ grid.addListener('afteredit', handleEdit);//give event name, handler (can use 'on' shorthand for addListener) //instead of adding listeners individually could have also loaded together like so: /* grid.addListener({ //same as saying grid.on 'rowcontextmenu': onMessageContextMenu, 'afteredit': handleEdit //note other listeners are same just without the 'on' (mousever, mouseout, click, etc.) }); */
/** * Grid rendering effects * if we want to render rows depending on values in row */ grid.getView().getRowClass = function(record, index) { switch (record.data.stars) { case '0': //right now just keying off of stars = '0' to signify the row has //not been saved yet, might be better to key from when the 'id' is no longer zero //but for now I just chose the last column thinking that would be the last to get updated return "yellowrow" break case '5': return "greenrow" break case '4': return "pinkrow" break default: //something else? } };//end row rendering
// This line to highlight the first row of the grid is quite finicky. There's probably // a better way to do this that is more reliable. It seems like this next line may get // executed before the grid is rendered and may not fire depending on the timing. grid.getSelectionModel().selectFirstRow(); }//end function buildGrid
/*--Private Area--*/ /******************/ /*****************/ /*--Public Area--*/ return {//returns an object Ext.myNameSpace.module1 with the following methods: /*--Public Properties--*/ // it's good practice to put the following in the public area of the module: // text used by module, default dmensions, styles, customizable options, etc. myPublicProperty: "I'm accessible as myNameSpace.myModule.myPublicProperty.", perPage: 50, //page limit
/*--Public Methods--*/ // Public methods can be called from outside // Public methods can access Private Area myPublicMethod: function(){//accessible as myNameSpace.myModule.myPublicMethod var myOtherProperty = this.myPublicProperty //use 'this' to refer to Public Property return myPrivateVar; //note reference to Private variable does NOT use 'this' }, init : function(){ //this method is called by the last line below that looks like this: //Ext.onReady(myNameSpace.myModule.init, myNameSpace.myModule, true); //So once the document is fully loaded that line gets executed and we //end up here. As a result, this is a good place to put DOM dependent tasks
/* Put initialization code here */
/** * Set up plugin for a check column * @param {Object} config */ Ext.grid.CheckColumn = function(config){ this.addEvents({ click: true }); Ext.grid.CheckColumn.superclass.constructor.call(this); Ext.apply(this, config, { init : function(grid){ this.grid = grid; this.grid.on('render', function(){ var view = this.grid.getView(); view.mainBody.on('mousedown', this.onMouseDown, this); }, this); }, onMouseDown : function(e, t){ if(t.className && t.className.indexOf('x-grid3-cc-'+this.id) != -1){ e.stopEvent(); var index = this.grid.getView().findRowIndex(t); var record = this.grid.store.getAt(index); record.set(this.dataIndex, !record.data[this.dataIndex]); this.fireEvent('click', this, e, record); } },
renderer : function(v, p, record){ var checkState = (+v) ? '-on' : '';//the +v type converts to a number (json returns a string which always evaluates true) p.css += ' x-grid3-check-col-td'; //return '<div class="x-grid3-check-col'+(v?'-on':'')+' x-grid3-cc-'+this.id+'"> </div>'; return '<div class="x-grid3-check-col'+ checkState +' x-grid3-cc-'+this.id+'"> </div>'; } }); if(!this.id){ this.id = Ext.id(); } this.renderer = this.renderer.createDelegate(this); }; Ext.extend(Ext.grid.CheckColumn, Ext.util.Observable);// extend Ext.util.Observable
//done with plugin setup ///////////////////////////////////////////////////////////////// // get our Grid! this.getMyGrid();// this = refers to properties and methods inside the public area // Works without this, used for state awareness... //Ext.state.Manager.setProvider(new Ext.state.CookieProvider()); },//end of init /** * getMyGrid * Call after initialization to get our Grid */ getMyGrid: function() { Ext.QuickTips.init();// Enable Quicktips setupDataSource(); buildGrid(); }, //a method we can call from Firebug console to check what is in our Data Store getDataSource: function() { return ds; } } /*--Public Area--*/ /*****************/ }(); /* End of application. Note the parentheses () — this notation causes the anonymous function to execute immediately, returning the object containing myPublicProperty and myPublicMethod. As soon as the anonymous function returns, that returned object is addressable as myNameSpace.myModule. */
// Since the above code has already executed, we can access the init method immediately: Ext.onReady(myNameSpace.myModule.init, myNameSpace.myModule, true); /* This line executes the myModule.init method after the document has been completely loaded. This line also sets the myModule.init method scope to myModule, which means you can call Public attributes (methods and properties) with a preceding 'this'.*/
Last edited by mjlecomte; 11-24-2007 at 01:36 AM..
Reason: various edits and updates
|