123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658 |
- 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: [
- '<b>{first_name} {last_name}</b> ',
- 'Title: {title}<br/><br/>',
- '<b>Last Review</b> ',
- '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
-
- // destroy the existing chart
- existingRadarChart.destroy();
- // 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...');
- // 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.applyIf(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
- });
-
- });
|