X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/7a654f8d43fdb43d78b63d90528bed6e86b608cc..3789b528d8dd8aad4558e38e22d775bcab1cbd36:/examples/personel-review/reviewapp.js diff --git a/examples/personel-review/reviewapp.js b/examples/personel-review/reviewapp.js new file mode 100644 index 00000000..b103f97a --- /dev/null +++ b/examples/personel-review/reviewapp.js @@ -0,0 +1,658 @@ +Ext.require([ + '*' +]); + +Ext.BLANK_IMAGE_URL = '../libs/ext-4.0/resources/themes/images/default/tree/s.gif'; + +Ext.onReady(function() { + + // Employee Data Model + Ext.regModel('Employee', { + fields: [ + {name:'id', type:'int'}, + {name:'first_name', type:'string'}, + {name:'last_name', type:'string'}, + {name:'title', type:'string'} + ], + + hasMany: {model:'Review', name:'reviews'} + }); + + // Review Data Model + Ext.regModel('Review', { + fields: [ + {name:'review_date', label:'Date', type:'date', dateFormat:'d-m-Y'}, + {name:'attendance', label:'Attendance', type:'int'}, + {name:'attitude', label:'Attitude', type:'int'}, + {name:'communication', label:'Communication', type:'int'}, + {name:'excellence', label:'Excellence', type:'int'}, + {name:'skills', label:'Skills', type:'int'}, + {name:'teamwork', label:'Teamwork', type:'int'}, + {name:'employee_id', label:'Employee ID', type:'int'} + ], + + belongsTo: 'Employee' + }); + + // Instance of a Data Store to hold Employee records + var employeeStore = new Ext.data.Store({ + storeId:'employeeStore', + model:'Employee', + data:[ + {id:1, first_name:'Michael', last_name:'Scott', title:'Regional Manager'}, + {id:2, first_name:'Dwight', last_name:'Schrute', title:'Sales Rep'}, + {id:3, first_name:'Jim', last_name:'Halpert', title:'Sales Rep'}, + {id:4, first_name:'Pam', last_name:'Halpert', title:'Office Administrator'}, + {id:5, first_name:'Andy', last_name:'Bernard', title:'Sales Rep'}, + {id:6, first_name:'Stanley', last_name:'Hudson', title:'Sales Rep'}, + {id:7, first_name:'Phyllis', last_name:'Lapin-Vance', title:'Sales Rep'}, + {id:8, first_name:'Kevin', last_name:'Malone', title:'Accountant'}, + {id:9, first_name:'Angela', last_name:'Martin', title:'Senior Accountant'}, + {id:10, first_name:'Meredith', last_name:'Palmer', title:'Supplier Relations Rep'} + ], + autoLoad:true + }); + + /** + * App.RadarStore + * @extends Ext.data.Store + * This is a specialized Data Store with dynamically generated fields + * data reformating capabilities to transform Employee and Review data + * into the format required by the Radar Chart. + * + * The constructor demonstrates dynamically generating store fields. + * populateReviewScores() populates the store using records from + * the reviewStore which holds all the employee review scores. + * + * calculateAverageScores() iterates through each metric in the + * review and calculates an average across all available reviews. + * + * Most of the actual data population and updates done by + * addUpdateRecordFromReviews() and removeRecordFromReviews() + * called when add/update/delete events are triggered on the ReviewStore. + */ + Ext.define('App.RadarStore', { + extend: 'Ext.data.Store', + + constructor: function(config) { + config = config || {}; + var dynamicFields = ['metric', 'avg']; // initalize the non-dynamic fields first + + employeeStore.each(function(record){ // loops through all the employees to setup the dynamic fields + dynamicFields.push('eid_' + record.get('id')); + }); + + Ext.apply(config, { + storeId:'radarStore', // let's us look it up later using Ext.data.StoreMgr.lookup('radarStore') + fields:dynamicFields, + data:[] + }); + + App.RadarStore.superclass.constructor.call(this, config); + }, + + addUpdateRecordFromReviews: function(reviews) { + var me = this; + + Ext.Array.each(reviews, function(review, recordIndex, all) { // add a new radarStore record for each review record + var eid = 'eid_' + review.get('employee_id'); // creates a unique id for each employee column in the store + + review.fields.each(function(field) { + + if(field.name !== "employee_id" && field.name !== "review_date") { // filter out the fields we don't need + var metricRecord = me.findRecord('metric', field.name); // checks for an existing metric record in the store + if(metricRecord) { + metricRecord.set(eid, review.get(field.name)); // updates existing record with field value from review + } else { + var newRecord = {}; // creates a new object we can populate with dynamic keys and values to create a new record + newRecord[eid] = review.get(field.name); + newRecord['metric'] = field.label; + me.add(newRecord); + } + } + }); + }); + + this.calculateAverageScores(); // update average scores + }, + + /** + * Calculates an average for each metric across all employees. + * We use this to create the average series always shown in the Radar Chart. + */ + calculateAverageScores: function() { + var me = this; // keeps the store in scope during Ext.Array.each + var reviewStore = Ext.data.StoreMgr.lookup('reviewStore'); + + var Review = Ext.ModelMgr.getModel('Review'); + + Ext.Array.each(Review.prototype.fields.keys, function(fieldName) { // loop through the Review model fields and calculate average scores + if(fieldName !== "employee_id" && fieldName !== "review_date") { // ignore non-score fields + var avgScore = Math.round(reviewStore.average(fieldName)); // takes advantage of Ext.data.Store.average() + var record = me.findRecord('metric', fieldName); + + if(record) { + record.set('avg', avgScore); + } else { + me.add({metric:fieldName, avg:avgScore}); + } + } + }); + }, + + populateReviewScores: function() { + var reviewStore = Ext.data.StoreMgr.lookup('reviewStore'); + this.addUpdateRecordFromReviews(reviewStore.data.items); // add all the review records to this store + }, + + removeRecordFromReviews: function(reviews) { + var me = this; + Ext.Array.each(reviews, function(review, recordIndex, all) { + var eid = 'eid_' + review.get('employee_id'); + + me.each(function(record) { + delete record.data[eid]; + }); + }); + + // upate average scores + this.calculateAverageScores(); + } + }); // end App.RadarStore definition + + + /** Creates an instance of App.RadarStore here so we + * here so we can re-use it during the life of the app. + * Otherwise we'd have to create a new instance everytime + * refreshRadarChart() is run. + */ + var radarStore = new App.RadarStore(); + + var reviewStore = new Ext.data.Store({ + storeId:'reviewStore', + model:'Review', + data:[ + {review_date:'01-04-2011', attendance:10, attitude:6, communication:6, excellence:3, skills:3, teamwork:3, employee_id:1}, + {review_date:'01-04-2011', attendance:6, attitude:5, communication:2, excellence:8, skills:9, teamwork:5, employee_id:2}, + {review_date:'01-04-2011', attendance:5, attitude:4, communication:3, excellence:5, skills:6, teamwork:2, employee_id:3}, + {review_date:'01-04-2011', attendance:8, attitude:2, communication:4, excellence:2, skills:5, teamwork:6, employee_id:4}, + {review_date:'01-04-2011', attendance:4, attitude:1, communication:5, excellence:7, skills:5, teamwork:5, employee_id:5}, + {review_date:'01-04-2011', attendance:5, attitude:2, communication:4, excellence:7, skills:9, teamwork:8, employee_id:6}, + {review_date:'01-04-2011', attendance:10, attitude:7, communication:8, excellence:7, skills:3, teamwork:4, employee_id:7}, + {review_date:'01-04-2011', attendance:10, attitude:8, communication:8, excellence:4, skills:8, teamwork:7, employee_id:8}, + {review_date:'01-04-2011', attendance:6, attitude:4, communication:9, excellence:7, skills:6, teamwork:5, employee_id:9}, + {review_date:'01-04-2011', attendance:7, attitude:5, communication:9, excellence:4, skills:2, teamwork:4, employee_id:10} + ], + listeners: { + add:function(store, records, storeIndex) { + var radarStore = Ext.data.StoreMgr.lookup('radarStore'); + + if(radarStore) { // only add records if an instance of the rardarStore already exists + radarStore.addUpdateRecordFromReviews(records); // add a new radarStore records for new review records + } + }, // end add listener + update: function(store, record, operation) { + radarStore.addUpdateRecordFromReviews([record]); + refreshRadarChart(); + }, + remove: function(store, records, storeIndex) { + // update the radarStore and regenerate the radarChart + Ext.data.StoreMgr.lookup('radarStore').removeRecordFromReviews(records); + refreshRadarChart(); + } // end remove listener + } + }); + + /** + * App.PerformanceRadar + * @extends Ext.chart.Chart + * This is a specialized Radar Chart which we use to display employee + * performance reviews. + * + * The class will be registered with an xtype of 'performanceradar' + */ + Ext.define('App.PerformanceRadar', { + extend: 'Ext.chart.Chart', + alias: 'widget.performanceradar', // register xtype performanceradar + constructor: function(config) { + config = config || {}; + + this.setAverageSeries(config); // make sure average is always present + + Ext.apply(config, { + id:'radarchart', + theme:'Category2', + animate:true, + store: Ext.data.StoreMgr.lookup('radarStore'), + margin:'0 0 50 0', + width:350, + height:500, + insetPadding:80, + legend:{ + position: 'bottom' + }, + axes: [{ + type:'Radial', + position:'radial', + label:{ + display: true + } + }] + }); // end Ext.apply + + App.PerformanceRadar.superclass.constructor.call(this, config); + + }, // end constructor + + setAverageSeries: function(config) { + var avgSeries = { + type: 'radar', + xField: 'metric', + yField: 'avg', + title: 'Avg', + labelDisplay:'over', + showInLegend: true, + showMarkers: true, + markerCfg: { + radius: 5, + size: 5, + stroke:'#0677BD', + fill:'#0677BD' + }, + style: { + 'stroke-width': 2, + 'stroke':'#0677BD', + fill: 'none' + } + } + + if(config.series) { + config.series.push(avgSeries); // if a series is passed in then append the average to it + } else { + config.series = [avgSeries]; // if a series isn't passed just create average + } + } + + }); // end Ext.ux.Performance radar definition + + /** + * App.EmployeeDetail + * @extends Ext.Panel + * This is a specialized Panel which is used to show information about + * an employee and the reviews we have on record for them. + * + * This demonstrates adding 2 custom properties (tplMarkup and + * startingMarkup) to the class. It also overrides the initComponent + * method and adds a new method called updateDetail. + * + * The class will be registered with an xtype of 'employeedetail' + */ + Ext.define('App.EmployeeDetail', { + extend: 'Ext.panel.Panel', + // register the App.EmployeeDetail class with an xtype of employeedetail + alias: 'widget.employeedetail', + // add tplMarkup as a new property + tplMarkup: [ + '{first_name} {last_name}  ', + 'Title: {title}

', + 'Last Review  ', + 'Attendance: {attendance}  ', + 'Attitude: {attitude}  ', + 'Communication: {communication}  ', + 'Excellence: {excellence}  ', + 'Skills: {skills}  ', + 'Teamwork: {teamwork}' + ], + + height:90, + bodyPadding: 7, + // override initComponent to create and compile the template + // apply styles to the body of the panel + initComponent: function() { + this.tpl = new Ext.Template(this.tplMarkup); + + // call the superclass's initComponent implementation + App.EmployeeDetail.superclass.initComponent.call(this); + } + }); + + Ext.define('App.ReviewWindow', { + extend: 'Ext.window.Window', + + constructor: function(config) { + config = config || {}; + Ext.apply(config, { + title:'Employee Performance Review', + width:320, + height:420, + layout:'fit', + items:[{ + xtype:'form', + id:'employeereviewcomboform', + fieldDefaults: { + labelAlign: 'left', + labelWidth: 90, + anchor: '100%' + }, + bodyPadding:5, + items:[{ + xtype:'fieldset', + title:'Employee Info', + items:[{ + xtype:'hiddenfield', + name:'employee_id' + },{ + xtype:'textfield', + name:'first_name', + fieldLabel:'First Name', + allowBlank:false + },{ + xtype:'textfield', + name:'last_name', + fieldLabel:'Last Name', + allowBlank:false + },{ + xtype:'textfield', + name:'title', + fieldLabel:'Title', + allowBlank:false + }] + },{ + xtype:'fieldset', + title:'Performance Review', + items:[{ + xtype:'datefield', + name:'review_date', + fieldLabel:'Review Date', + format:'d-m-Y', + maxValue: new Date(), + value: new Date(), + allowBlank:false + },{ + xtype:'slider', + name:'attendance', + fieldLabel:'Attendance', + value:5, + increment:1, + minValue:1, + maxValue:10 + },{ + xtype:'slider', + name:'attitude', + fieldLabel:'Attitude', + value:5, + minValue: 1, + maxValue: 10 + },{ + xtype:'slider', + name:'communication', + fieldLabel:'Communication', + value:5, + increment:1, + minValue:1, + maxValue:10 + },{ + xtype:'numberfield', + name:'excellence', + fieldLabel:'Excellence', + value:5, + minValue: 1, + maxValue: 10 + },{ + xtype:'numberfield', + name:'skills', + fieldLabel:'Skills', + value:5, + minValue: 1, + maxValue: 10 + },{ + xtype:'numberfield', + name:'teamwork', + fieldLabel:'Teamwork', + value:5, + minValue: 1, + maxValue: 10 + }] + }] + }], + buttons:[{ + text:'Cancel', + width:80, + handler:function() { + this.up('window').close(); + } + }, + { + text:'Save', + width:80, + handler:function(btn, eventObj) { + var window = btn.up('window'); + var form = window.down('form').getForm(); + + if (form.isValid()) { + window.getEl().mask('saving data...'); + var vals = form.getValues(); + var employeeStore = Ext.data.StoreMgr.lookup('employeeStore'); + var currentEmployee = employeeStore.findRecord('id', vals['employee_id']); + + // look up id for this employee to see if they already exist + if(vals['employee_id'] && currentEmployee) { + currentEmployee.set('first_name', vals['first_name']); + currentEmployee.set('last_name', vals['last_name']); + currentEmployee.set('title', vals['title']); + + var currentReview = Ext.data.StoreMgr.lookup('reviewStore').findRecord('employee_id', vals['employee_id']); + currentReview.set('review_date', vals['review_date']); + currentReview.set('attendance', vals['attendance']); + currentReview.set('attitude', vals['attitude']); + currentReview.set('communication', vals['communication']); + currentReview.set('excellence', vals['excellence']); + currentReview.set('skills', vals['skills']); + currentReview.set('teamwork', vals['teamwork']); + } else { + var newId = employeeStore.getCount() + 1; + + employeeStore.add({ + id: newId, + first_name: vals['first_name'], + last_name: vals['last_name'], + title: vals['title'] + }); + + Ext.data.StoreMgr.lookup('reviewStore').add({ + review_date: vals['review_date'], + attendance: vals['attendance'], + attitude: vals['attitude'], + communication: vals['communication'], + excellence: vals['excellence'], + skills: vals['skills'], + teamwork: vals['teamwork'], + employee_id: newId + }); + } + window.getEl().unmask(); + window.close(); + } + } + }] + }); // end Ext.apply + + App.ReviewWindow.superclass.constructor.call(this, config); + + } // end constructor + + }); + + + // adds a record to the radar chart store and + // creates a series in the chart for selected employees + function refreshRadarChart(employees) { + employees = employees || []; // in case its called with nothing we'll at least have an empty array + var existingRadarChart = Ext.getCmp('radarchart'); // grab the radar chart component (used down below) + var reportsPanel = Ext.getCmp('reportspanel'); // grab the reports panel component (used down below) + var dynamicSeries = []; // setup an array of chart series that we'll create dynamically + + for(var index = 0; index < employees.length; index++) { + var fullName = employees[index].get('first_name') + ' ' + employees[index].get('last_name'); + var eid = 'eid_' + employees[index].get('id'); + + // add to the dynamic series we're building + dynamicSeries.push({ + type: 'radar', + title: fullName, + xField: 'metric', + yField: eid, + labelDisplay: 'over', + showInLegend: true, + showMarkers: true, + markerCfg: { + radius: 5, + size: 5 + }, + style: { + 'stroke-width': 2, + fill: 'none' + } + }); + + } // end for loop + + // create the new chart using the dynamic series we just made + var newRadarChart = new App.PerformanceRadar({series:dynamicSeries}); + // mask the panel while we switch out charts + reportsPanel.getEl().mask('updating chart...'); + // destroy the existing chart + existingRadarChart.destroy(); + // display the new one + reportsPanel.add(newRadarChart); + // un mask the reports panel + reportsPanel.getEl().unmask(); + } + + function refreshEmployeeDetails(employees) { + var detailsPanel = Ext.getCmp('detailspanel'); + var reviewStore = Ext.data.StoreMgr.lookup('reviewStore'); + var items = []; + + for(var index = 0; index < employees.length; index++) { + var templateData = Ext.merge(employees[index].data, reviewStore.findRecord('employee_id', employees[index].get('id')).data); + var employeePanel = new App.EmployeeDetail({ + title:employees[index].get('first_name') + ' ' + employees[index].get('last_name'), + data:templateData // combined employee and latest review dataTransfer + }); + items.push(employeePanel); + } + + detailsPanel.getEl().mask('updating details...'); + detailsPanel.removeAll(); + detailsPanel.add(items); + detailsPanel.getEl().unmask(); + } + + // sets Up Checkbox Selection Model for the Employee Grid + var checkboxSelModel = new Ext.selection.CheckboxModel(); + + var viewport = new Ext.container.Viewport({ + id:'mainviewport', + layout: 'border', // sets up Ext.layout.container.Border + items: [{ + xtype:'panel', + region:'center', + layout:'auto', + autoScroll:true, + title:'Employee Performance Manager', + tbar:[{ + text:'Add Employee', + tooltip:'Add a new employee', + iconCls:'add', + handler:function() { // display a window to add a new employee + new App.ReviewWindow().show(); + } + }], + items:[{ + xtype:'grid', + store:Ext.data.StoreMgr.lookup('employeeStore'), + height:300, + columns:[{ + text:'First Name', + dataIndex:'first_name', + flex:2 + }, + { + text:'Last Name', + dataIndex:'last_name', + flex:2 + }, + { + text:'Title', + dataIndex:'title', + flex:3 + }, + { + xtype:'actioncolumn', + width:45, + items:[{ + icon:'images/edit.png', + tooltip:'Edit Employee', + handler:function(grid, rowIndex, colIndex) { + var employee = grid.getStore().getAt(rowIndex); + var review = reviewStore.findRecord('employee_id', employee.get('id')); + var win = new App.ReviewWindow({hidden:true}); + var form = win.down('form').getForm(); + form.loadRecord(employee); + form.loadRecord(review); + win.show(); + } + }, + { + icon:'images/delete.png', + tooltip:'Delete Employee', + width:75, + handler:function(grid, rowIndex, colIndex) { + Ext.Msg.confirm('Remove Employee?', 'Are you sure you want to remove this employee?', + function(choice) { + if(choice === 'yes') { + var reviewStore = Ext.data.StoreMgr.lookup('reviewStore'); + + var employee = grid.getStore().getAt(rowIndex); + var reviewIndex = reviewStore.find('employee_id', employee.get('id')); + reviewStore.removeAt(reviewIndex); + grid.getStore().removeAt(rowIndex); + } + } + ); + } + }] + }], + selModel: new Ext.selection.CheckboxModel(), + columnLines: true, + viewConfig: {stripeRows:true}, + listeners:{ + selectionchange:function(selModel, selected) { + refreshRadarChart(selected); + refreshEmployeeDetails(selected); + } + } + },{ + xtype:'container', + id:'detailspanel', + layout:{ + type:'vbox', + align:'stretch', + autoSize:true + } + }] + },{ + xtype:'panel', // sets up the chart panel (starts collapsed) + region:'east', + id:'reportspanel', + title:'Performance Report', + width:350, + layout: 'fit', + items:[{ + xtype:'performanceradar' // this instantiates a App.PerformanceRadar object + }] + }] // mainviewport items array ends here + }); + +}); \ No newline at end of file