3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
19 Ext.BLANK_IMAGE_URL = '../libs/ext-4.0/resources/themes/images/default/tree/s.gif';
21 Ext.onReady(function() {
23 // Employee Data Model
24 Ext.regModel('Employee', {
26 {name:'id', type:'int'},
27 {name:'first_name', type:'string'},
28 {name:'last_name', type:'string'},
29 {name:'title', type:'string'}
32 hasMany: {model:'Review', name:'reviews'}
36 Ext.regModel('Review', {
38 {name:'review_date', label:'Date', type:'date', dateFormat:'d-m-Y'},
39 {name:'attendance', label:'Attendance', type:'int'},
40 {name:'attitude', label:'Attitude', type:'int'},
41 {name:'communication', label:'Communication', type:'int'},
42 {name:'excellence', label:'Excellence', type:'int'},
43 {name:'skills', label:'Skills', type:'int'},
44 {name:'teamwork', label:'Teamwork', type:'int'},
45 {name:'employee_id', label:'Employee ID', type:'int'}
51 // Instance of a Data Store to hold Employee records
52 var employeeStore = new Ext.data.Store({
53 storeId:'employeeStore',
56 {id:1, first_name:'Michael', last_name:'Scott', title:'Regional Manager'},
57 {id:2, first_name:'Dwight', last_name:'Schrute', title:'Sales Rep'},
58 {id:3, first_name:'Jim', last_name:'Halpert', title:'Sales Rep'},
59 {id:4, first_name:'Pam', last_name:'Halpert', title:'Office Administrator'},
60 {id:5, first_name:'Andy', last_name:'Bernard', title:'Sales Rep'},
61 {id:6, first_name:'Stanley', last_name:'Hudson', title:'Sales Rep'},
62 {id:7, first_name:'Phyllis', last_name:'Lapin-Vance', title:'Sales Rep'},
63 {id:8, first_name:'Kevin', last_name:'Malone', title:'Accountant'},
64 {id:9, first_name:'Angela', last_name:'Martin', title:'Senior Accountant'},
65 {id:10, first_name:'Meredith', last_name:'Palmer', title:'Supplier Relations Rep'}
72 * @extends Ext.data.Store
73 * This is a specialized Data Store with dynamically generated fields
74 * data reformating capabilities to transform Employee and Review data
75 * into the format required by the Radar Chart.
77 * The constructor demonstrates dynamically generating store fields.
78 * populateReviewScores() populates the store using records from
79 * the reviewStore which holds all the employee review scores.
81 * calculateAverageScores() iterates through each metric in the
82 * review and calculates an average across all available reviews.
84 * Most of the actual data population and updates done by
85 * addUpdateRecordFromReviews() and removeRecordFromReviews()
86 * called when add/update/delete events are triggered on the ReviewStore.
88 Ext.define('App.RadarStore', {
89 extend: 'Ext.data.Store',
91 constructor: function(config) {
92 config = config || {};
93 var dynamicFields = ['metric', 'avg']; // initalize the non-dynamic fields first
95 employeeStore.each(function(record){ // loops through all the employees to setup the dynamic fields
96 dynamicFields.push('eid_' + record.get('id'));
100 storeId:'radarStore', // let's us look it up later using Ext.data.StoreMgr.lookup('radarStore')
101 fields:dynamicFields,
105 App.RadarStore.superclass.constructor.call(this, config);
108 addUpdateRecordFromReviews: function(reviews) {
111 Ext.Array.each(reviews, function(review, recordIndex, all) { // add a new radarStore record for each review record
112 var eid = 'eid_' + review.get('employee_id'); // creates a unique id for each employee column in the store
114 review.fields.each(function(field) {
116 if(field.name !== "employee_id" && field.name !== "review_date") { // filter out the fields we don't need
117 var metricRecord = me.findRecord('metric', field.name); // checks for an existing metric record in the store
119 metricRecord.set(eid, review.get(field.name)); // updates existing record with field value from review
121 var newRecord = {}; // creates a new object we can populate with dynamic keys and values to create a new record
122 newRecord[eid] = review.get(field.name);
123 newRecord['metric'] = field.label;
130 this.calculateAverageScores(); // update average scores
134 * Calculates an average for each metric across all employees.
135 * We use this to create the average series always shown in the Radar Chart.
137 calculateAverageScores: function() {
138 var me = this; // keeps the store in scope during Ext.Array.each
139 var reviewStore = Ext.data.StoreMgr.lookup('reviewStore');
141 var Review = Ext.ModelMgr.getModel('Review');
143 Ext.Array.each(Review.prototype.fields.keys, function(fieldName) { // loop through the Review model fields and calculate average scores
144 if(fieldName !== "employee_id" && fieldName !== "review_date") { // ignore non-score fields
145 var avgScore = Math.round(reviewStore.average(fieldName)); // takes advantage of Ext.data.Store.average()
146 var record = me.findRecord('metric', fieldName);
149 record.set('avg', avgScore);
151 me.add({metric:fieldName, avg:avgScore});
157 populateReviewScores: function() {
158 var reviewStore = Ext.data.StoreMgr.lookup('reviewStore');
159 this.addUpdateRecordFromReviews(reviewStore.data.items); // add all the review records to this store
162 removeRecordFromReviews: function(reviews) {
164 Ext.Array.each(reviews, function(review, recordIndex, all) {
165 var eid = 'eid_' + review.get('employee_id');
167 me.each(function(record) {
168 delete record.data[eid];
172 // upate average scores
173 this.calculateAverageScores();
175 }); // end App.RadarStore definition
178 /** Creates an instance of App.RadarStore here so we
179 * here so we can re-use it during the life of the app.
180 * Otherwise we'd have to create a new instance everytime
181 * refreshRadarChart() is run.
183 var radarStore = new App.RadarStore();
185 var reviewStore = new Ext.data.Store({
186 storeId:'reviewStore',
189 {review_date:'01-04-2011', attendance:10, attitude:6, communication:6, excellence:3, skills:3, teamwork:3, employee_id:1},
190 {review_date:'01-04-2011', attendance:6, attitude:5, communication:2, excellence:8, skills:9, teamwork:5, employee_id:2},
191 {review_date:'01-04-2011', attendance:5, attitude:4, communication:3, excellence:5, skills:6, teamwork:2, employee_id:3},
192 {review_date:'01-04-2011', attendance:8, attitude:2, communication:4, excellence:2, skills:5, teamwork:6, employee_id:4},
193 {review_date:'01-04-2011', attendance:4, attitude:1, communication:5, excellence:7, skills:5, teamwork:5, employee_id:5},
194 {review_date:'01-04-2011', attendance:5, attitude:2, communication:4, excellence:7, skills:9, teamwork:8, employee_id:6},
195 {review_date:'01-04-2011', attendance:10, attitude:7, communication:8, excellence:7, skills:3, teamwork:4, employee_id:7},
196 {review_date:'01-04-2011', attendance:10, attitude:8, communication:8, excellence:4, skills:8, teamwork:7, employee_id:8},
197 {review_date:'01-04-2011', attendance:6, attitude:4, communication:9, excellence:7, skills:6, teamwork:5, employee_id:9},
198 {review_date:'01-04-2011', attendance:7, attitude:5, communication:9, excellence:4, skills:2, teamwork:4, employee_id:10}
201 add:function(store, records, storeIndex) {
202 var radarStore = Ext.data.StoreMgr.lookup('radarStore');
204 if(radarStore) { // only add records if an instance of the rardarStore already exists
205 radarStore.addUpdateRecordFromReviews(records); // add a new radarStore records for new review records
207 }, // end add listener
208 update: function(store, record, operation) {
209 radarStore.addUpdateRecordFromReviews([record]);
212 remove: function(store, records, storeIndex) {
213 // update the radarStore and regenerate the radarChart
214 Ext.data.StoreMgr.lookup('radarStore').removeRecordFromReviews(records);
216 } // end remove listener
221 * App.PerformanceRadar
222 * @extends Ext.chart.Chart
223 * This is a specialized Radar Chart which we use to display employee
224 * performance reviews.
226 * The class will be registered with an xtype of 'performanceradar'
228 Ext.define('App.PerformanceRadar', {
229 extend: 'Ext.chart.Chart',
230 alias: 'widget.performanceradar', // register xtype performanceradar
231 constructor: function(config) {
232 config = config || {};
234 this.setAverageSeries(config); // make sure average is always present
240 store: Ext.data.StoreMgr.lookup('radarStore'),
257 App.PerformanceRadar.superclass.constructor.call(this, config);
259 }, // end constructor
261 setAverageSeries: function(config) {
284 config.series.push(avgSeries); // if a series is passed in then append the average to it
286 config.series = [avgSeries]; // if a series isn't passed just create average
290 }); // end Ext.ux.Performance radar definition
295 * This is a specialized Panel which is used to show information about
296 * an employee and the reviews we have on record for them.
298 * This demonstrates adding 2 custom properties (tplMarkup and
299 * startingMarkup) to the class. It also overrides the initComponent
300 * method and adds a new method called updateDetail.
302 * The class will be registered with an xtype of 'employeedetail'
304 Ext.define('App.EmployeeDetail', {
305 extend: 'Ext.panel.Panel',
306 // register the App.EmployeeDetail class with an xtype of employeedetail
307 alias: 'widget.employeedetail',
308 // add tplMarkup as a new property
310 '<b>{first_name} {last_name}</b> ',
311 'Title: {title}<br/><br/>',
312 '<b>Last Review</b> ',
313 'Attendance: {attendance} ',
314 'Attitude: {attitude} ',
315 'Communication: {communication} ',
316 'Excellence: {excellence} ',
317 'Skills: {skills} ',
318 'Teamwork: {teamwork}'
323 // override initComponent to create and compile the template
324 // apply styles to the body of the panel
325 initComponent: function() {
326 this.tpl = new Ext.Template(this.tplMarkup);
328 // call the superclass's initComponent implementation
329 App.EmployeeDetail.superclass.initComponent.call(this);
333 Ext.define('App.ReviewWindow', {
334 extend: 'Ext.window.Window',
336 constructor: function(config) {
337 config = config || {};
339 title:'Employee Performance Review',
345 id:'employeereviewcomboform',
354 title:'Employee Info',
361 fieldLabel:'First Name',
366 fieldLabel:'Last Name',
376 title:'Performance Review',
380 fieldLabel:'Review Date',
382 maxValue: new Date(),
388 fieldLabel:'Attendance',
396 fieldLabel:'Attitude',
402 name:'communication',
403 fieldLabel:'Communication',
411 fieldLabel:'Excellence',
425 fieldLabel:'Teamwork',
436 this.up('window').close();
442 handler:function(btn, eventObj) {
443 var window = btn.up('window');
444 var form = window.down('form').getForm();
446 if (form.isValid()) {
447 window.getEl().mask('saving data...');
448 var vals = form.getValues();
449 var employeeStore = Ext.data.StoreMgr.lookup('employeeStore');
450 var currentEmployee = employeeStore.findRecord('id', vals['employee_id']);
452 // look up id for this employee to see if they already exist
453 if(vals['employee_id'] && currentEmployee) {
454 currentEmployee.set('first_name', vals['first_name']);
455 currentEmployee.set('last_name', vals['last_name']);
456 currentEmployee.set('title', vals['title']);
458 var currentReview = Ext.data.StoreMgr.lookup('reviewStore').findRecord('employee_id', vals['employee_id']);
459 currentReview.set('review_date', vals['review_date']);
460 currentReview.set('attendance', vals['attendance']);
461 currentReview.set('attitude', vals['attitude']);
462 currentReview.set('communication', vals['communication']);
463 currentReview.set('excellence', vals['excellence']);
464 currentReview.set('skills', vals['skills']);
465 currentReview.set('teamwork', vals['teamwork']);
467 var newId = employeeStore.getCount() + 1;
471 first_name: vals['first_name'],
472 last_name: vals['last_name'],
476 Ext.data.StoreMgr.lookup('reviewStore').add({
477 review_date: vals['review_date'],
478 attendance: vals['attendance'],
479 attitude: vals['attitude'],
480 communication: vals['communication'],
481 excellence: vals['excellence'],
482 skills: vals['skills'],
483 teamwork: vals['teamwork'],
487 window.getEl().unmask();
494 App.ReviewWindow.superclass.constructor.call(this, config);
501 // adds a record to the radar chart store and
502 // creates a series in the chart for selected employees
503 function refreshRadarChart(employees) {
504 employees = employees || []; // in case its called with nothing we'll at least have an empty array
505 var existingRadarChart = Ext.getCmp('radarchart'); // grab the radar chart component (used down below)
506 var reportsPanel = Ext.getCmp('reportspanel'); // grab the reports panel component (used down below)
507 var dynamicSeries = []; // setup an array of chart series that we'll create dynamically
509 for(var index = 0; index < employees.length; index++) {
510 var fullName = employees[index].get('first_name') + ' ' + employees[index].get('last_name');
511 var eid = 'eid_' + employees[index].get('id');
513 // add to the dynamic series we're building
519 labelDisplay: 'over',
534 // create the new chart using the dynamic series we just made
535 var newRadarChart = new App.PerformanceRadar({series:dynamicSeries});
536 // mask the panel while we switch out charts
537 reportsPanel.getEl().mask('updating chart...');
538 // destroy the existing chart
539 existingRadarChart.destroy();
540 // display the new one
541 reportsPanel.add(newRadarChart);
542 // un mask the reports panel
543 reportsPanel.getEl().unmask();
546 function refreshEmployeeDetails(employees) {
547 var detailsPanel = Ext.getCmp('detailspanel');
548 var reviewStore = Ext.data.StoreMgr.lookup('reviewStore');
551 for(var index = 0; index < employees.length; index++) {
552 var templateData = Ext.merge(employees[index].data, reviewStore.findRecord('employee_id', employees[index].get('id')).data);
553 var employeePanel = new App.EmployeeDetail({
554 title:employees[index].get('first_name') + ' ' + employees[index].get('last_name'),
555 data:templateData // combined employee and latest review dataTransfer
557 items.push(employeePanel);
560 detailsPanel.getEl().mask('updating details...');
561 detailsPanel.removeAll();
562 detailsPanel.add(items);
563 detailsPanel.getEl().unmask();
566 // sets Up Checkbox Selection Model for the Employee Grid
567 var checkboxSelModel = new Ext.selection.CheckboxModel();
569 var viewport = new Ext.container.Viewport({
571 layout: 'border', // sets up Ext.layout.container.Border
577 title:'Employee Performance Manager',
580 tooltip:'Add a new employee',
582 handler:function() { // display a window to add a new employee
583 new App.ReviewWindow().show();
588 store:Ext.data.StoreMgr.lookup('employeeStore'),
592 dataIndex:'first_name',
597 dataIndex:'last_name',
606 xtype:'actioncolumn',
609 icon:'images/edit.png',
610 tooltip:'Edit Employee',
611 handler:function(grid, rowIndex, colIndex) {
612 var employee = grid.getStore().getAt(rowIndex);
613 var review = reviewStore.findRecord('employee_id', employee.get('id'));
614 var win = new App.ReviewWindow({hidden:true});
615 var form = win.down('form').getForm();
616 form.loadRecord(employee);
617 form.loadRecord(review);
622 icon:'images/delete.png',
623 tooltip:'Delete Employee',
625 handler:function(grid, rowIndex, colIndex) {
626 Ext.Msg.confirm('Remove Employee?', 'Are you sure you want to remove this employee?',
628 if(choice === 'yes') {
629 var reviewStore = Ext.data.StoreMgr.lookup('reviewStore');
631 var employee = grid.getStore().getAt(rowIndex);
632 var reviewIndex = reviewStore.find('employee_id', employee.get('id'));
633 reviewStore.removeAt(reviewIndex);
634 grid.getStore().removeAt(rowIndex);
641 selModel: new Ext.selection.CheckboxModel(),
643 viewConfig: {stripeRows:true},
645 selectionchange:function(selModel, selected) {
646 refreshRadarChart(selected);
647 refreshEmployeeDetails(selected);
660 xtype:'panel', // sets up the chart panel (starts collapsed)
663 title:'Performance Report',
667 xtype:'performanceradar' // this instantiates a App.PerformanceRadar object
669 }] // mainviewport items array ends here