]> git.proxmox.com Git - extjs.git/blob - extjs/build/examples/classic/personel-review/reviewapp.js
add extjs 6.0.1 sources
[extjs.git] / extjs / build / examples / classic / personel-review / reviewapp.js
1 Ext.require([
2 '*'
3 ]);
4
5 Ext.BLANK_IMAGE_URL = '../libs/ext-4.0/resources/themes/images/default/tree/s.gif';
6
7 Ext.onReady(function() {
8
9 // Employee Data Model
10 Ext.regModel('Employee', {
11 fields: [
12 {name:'id', type:'int'},
13 {name:'first_name', type:'string'},
14 {name:'last_name', type:'string'},
15 {name:'title', type:'string'}
16 ],
17
18 hasMany: {model:'Review', name:'reviews'}
19 });
20
21 // Review Data Model
22 Ext.regModel('Review', {
23 fields: [
24 {name:'review_date', label:'Date', type:'date', dateFormat:'d-m-Y'},
25 {name:'attendance', label:'Attendance', type:'int'},
26 {name:'attitude', label:'Attitude', type:'int'},
27 {name:'communication', label:'Communication', type:'int'},
28 {name:'excellence', label:'Excellence', type:'int'},
29 {name:'skills', label:'Skills', type:'int'},
30 {name:'teamwork', label:'Teamwork', type:'int'},
31 {name:'employee_id', label:'Employee ID', type:'int'}
32 ],
33
34 belongsTo: 'Employee'
35 });
36
37 // Instance of a Data Store to hold Employee records
38 var employeeStore = new Ext.data.Store({
39 storeId:'employeeStore',
40 model:'Employee',
41 data:[
42 {id:1, first_name:'Michael', last_name:'Scott', title:'Regional Manager'},
43 {id:2, first_name:'Dwight', last_name:'Schrute', title:'Sales Rep'},
44 {id:3, first_name:'Jim', last_name:'Halpert', title:'Sales Rep'},
45 {id:4, first_name:'Pam', last_name:'Halpert', title:'Office Administrator'},
46 {id:5, first_name:'Andy', last_name:'Bernard', title:'Sales Rep'},
47 {id:6, first_name:'Stanley', last_name:'Hudson', title:'Sales Rep'},
48 {id:7, first_name:'Phyllis', last_name:'Lapin-Vance', title:'Sales Rep'},
49 {id:8, first_name:'Kevin', last_name:'Malone', title:'Accountant'},
50 {id:9, first_name:'Angela', last_name:'Martin', title:'Senior Accountant'},
51 {id:10, first_name:'Meredith', last_name:'Palmer', title:'Supplier Relations Rep'}
52 ],
53 autoLoad:true
54 });
55
56 /**
57 * App.RadarStore
58 * @extends Ext.data.Store
59 * This is a specialized Data Store with dynamically generated fields
60 * data reformating capabilities to transform Employee and Review data
61 * into the format required by the Radar Chart.
62 *
63 * The constructor demonstrates dynamically generating store fields.
64 * populateReviewScores() populates the store using records from
65 * the reviewStore which holds all the employee review scores.
66 *
67 * calculateAverageScores() iterates through each metric in the
68 * review and calculates an average across all available reviews.
69 *
70 * Most of the actual data population and updates done by
71 * addUpdateRecordFromReviews() and removeRecordFromReviews()
72 * called when add/update/delete events are triggered on the ReviewStore.
73 */
74 Ext.define('App.RadarStore', {
75 extend: 'Ext.data.Store',
76
77 constructor: function(config) {
78 config = config || {};
79 var dynamicFields = ['metric', 'avg']; // initalize the non-dynamic fields first
80
81 employeeStore.each(function(record){ // loops through all the employees to setup the dynamic fields
82 dynamicFields.push('eid_' + record.get('id'));
83 });
84
85 Ext.apply(config, {
86 storeId:'radarStore', // let's us look it up later using Ext.data.StoreMgr.lookup('radarStore')
87 fields:dynamicFields,
88 data:[]
89 });
90
91 App.RadarStore.superclass.constructor.call(this, config);
92 },
93
94 addUpdateRecordFromReviews: function(reviews) {
95 var me = this;
96
97 Ext.Array.each(reviews, function(review, recordIndex, all) { // add a new radarStore record for each review record
98 var eid = 'eid_' + review.get('employee_id'); // creates a unique id for each employee column in the store
99
100 review.fields.each(function(field) {
101
102 if(field.name !== "employee_id" && field.name !== "review_date") { // filter out the fields we don't need
103 var metricRecord = me.findRecord('metric', field.name); // checks for an existing metric record in the store
104 if(metricRecord) {
105 metricRecord.set(eid, review.get(field.name)); // updates existing record with field value from review
106 } else {
107 var newRecord = {}; // creates a new object we can populate with dynamic keys and values to create a new record
108 newRecord[eid] = review.get(field.name);
109 newRecord['metric'] = field.label;
110 me.add(newRecord);
111 }
112 }
113 });
114 });
115
116 this.calculateAverageScores(); // update average scores
117 },
118
119 /**
120 * Calculates an average for each metric across all employees.
121 * We use this to create the average series always shown in the Radar Chart.
122 */
123 calculateAverageScores: function() {
124 var me = this; // keeps the store in scope during Ext.Array.each
125 var reviewStore = Ext.data.StoreMgr.lookup('reviewStore');
126
127 var Review = Ext.ModelMgr.getModel('Review');
128
129 Ext.Array.each(Review.prototype.fields.keys, function(fieldName) { // loop through the Review model fields and calculate average scores
130 if(fieldName !== "employee_id" && fieldName !== "review_date") { // ignore non-score fields
131 var avgScore = Math.round(reviewStore.average(fieldName)); // takes advantage of Ext.data.Store.average()
132 var record = me.findRecord('metric', fieldName);
133
134 if(record) {
135 record.set('avg', avgScore);
136 } else {
137 me.add({metric:fieldName, avg:avgScore});
138 }
139 }
140 });
141 },
142
143 populateReviewScores: function() {
144 var reviewStore = Ext.data.StoreMgr.lookup('reviewStore');
145 this.addUpdateRecordFromReviews(reviewStore.data.items); // add all the review records to this store
146 },
147
148 removeRecordFromReviews: function(reviews) {
149 var me = this;
150 Ext.Array.each(reviews, function(review, recordIndex, all) {
151 var eid = 'eid_' + review.get('employee_id');
152
153 me.each(function(record) {
154 delete record.data[eid];
155 });
156 });
157
158 // upate average scores
159 this.calculateAverageScores();
160 }
161 }); // end App.RadarStore definition
162
163
164 /** Creates an instance of App.RadarStore here so we
165 * here so we can re-use it during the life of the app.
166 * Otherwise we'd have to create a new instance everytime
167 * refreshRadarChart() is run.
168 */
169 var radarStore = new App.RadarStore();
170
171 var reviewStore = new Ext.data.Store({
172 storeId:'reviewStore',
173 model:'Review',
174 data:[
175 {review_date:'01-04-2011', attendance:10, attitude:6, communication:6, excellence:3, skills:3, teamwork:3, employee_id:1},
176 {review_date:'01-04-2011', attendance:6, attitude:5, communication:2, excellence:8, skills:9, teamwork:5, employee_id:2},
177 {review_date:'01-04-2011', attendance:5, attitude:4, communication:3, excellence:5, skills:6, teamwork:2, employee_id:3},
178 {review_date:'01-04-2011', attendance:8, attitude:2, communication:4, excellence:2, skills:5, teamwork:6, employee_id:4},
179 {review_date:'01-04-2011', attendance:4, attitude:1, communication:5, excellence:7, skills:5, teamwork:5, employee_id:5},
180 {review_date:'01-04-2011', attendance:5, attitude:2, communication:4, excellence:7, skills:9, teamwork:8, employee_id:6},
181 {review_date:'01-04-2011', attendance:10, attitude:7, communication:8, excellence:7, skills:3, teamwork:4, employee_id:7},
182 {review_date:'01-04-2011', attendance:10, attitude:8, communication:8, excellence:4, skills:8, teamwork:7, employee_id:8},
183 {review_date:'01-04-2011', attendance:6, attitude:4, communication:9, excellence:7, skills:6, teamwork:5, employee_id:9},
184 {review_date:'01-04-2011', attendance:7, attitude:5, communication:9, excellence:4, skills:2, teamwork:4, employee_id:10}
185 ],
186 listeners: {
187 add:function(store, records, storeIndex) {
188 var radarStore = Ext.data.StoreMgr.lookup('radarStore');
189
190 if(radarStore) { // only add records if an instance of the rardarStore already exists
191 radarStore.addUpdateRecordFromReviews(records); // add a new radarStore records for new review records
192 }
193 }, // end add listener
194 update: function(store, record, operation) {
195 radarStore.addUpdateRecordFromReviews([record]);
196 refreshRadarChart();
197 },
198 remove: function(store, records, storeIndex) {
199 // update the radarStore and regenerate the radarChart
200 Ext.data.StoreMgr.lookup('radarStore').removeRecordFromReviews(records);
201 refreshRadarChart();
202 } // end remove listener
203 }
204 });
205
206 /**
207 * App.PerformanceRadar
208 * @extends Ext.chart.Chart
209 * This is a specialized Radar Chart which we use to display employee
210 * performance reviews.
211 *
212 * The class will be registered with an xtype of 'performanceradar'
213 */
214 Ext.define('App.PerformanceRadar', {
215 extend: 'Ext.chart.Chart',
216 alias: 'widget.performanceradar', // register xtype performanceradar
217 constructor: function(config) {
218 config = config || {};
219
220 this.setAverageSeries(config); // make sure average is always present
221
222 Ext.apply(config, {
223 id:'radarchart',
224 theme:'Category2',
225 animate:true,
226 store: Ext.data.StoreMgr.lookup('radarStore'),
227 margin:'0 0 50 0',
228 width:350,
229 height:500,
230 insetPadding:80,
231 legend:{
232 position: 'bottom'
233 },
234 axes: [{
235 type:'Radial',
236 position:'radial',
237 label:{
238 display: true
239 }
240 }]
241 }); // end Ext.apply
242
243 App.PerformanceRadar.superclass.constructor.call(this, config);
244
245 }, // end constructor
246
247 setAverageSeries: function(config) {
248 var avgSeries = {
249 type: 'radar',
250 xField: 'metric',
251 yField: 'avg',
252 title: 'Avg',
253 labelDisplay:'over',
254 showInLegend: true,
255 showMarkers: true,
256 markerCfg: {
257 radius: 5,
258 size: 5,
259 stroke:'#0677BD',
260 fill:'#0677BD'
261 },
262 style: {
263 'stroke-width': 2,
264 'stroke':'#0677BD',
265 fill: 'none'
266 }
267 };
268
269 if(config.series) {
270 config.series.push(avgSeries); // if a series is passed in then append the average to it
271 } else {
272 config.series = [avgSeries]; // if a series isn't passed just create average
273 }
274 }
275
276 }); // end Ext.ux.Performance radar definition
277
278 /**
279 * App.EmployeeDetail
280 * @extends Ext.Panel
281 * This is a specialized Panel which is used to show information about
282 * an employee and the reviews we have on record for them.
283 *
284 * This demonstrates adding 2 custom properties (tplMarkup and
285 * startingMarkup) to the class. It also overrides the initComponent
286 * method and adds a new method called updateDetail.
287 *
288 * The class will be registered with an xtype of 'employeedetail'
289 */
290 Ext.define('App.EmployeeDetail', {
291 extend: 'Ext.panel.Panel',
292 // register the App.EmployeeDetail class with an xtype of employeedetail
293 alias: 'widget.employeedetail',
294 // add tplMarkup as a new property
295 tplMarkup: [
296 '<b>{first_name}&nbsp;{last_name}</b>&nbsp;&nbsp;',
297 'Title: {title}<br/><br/>',
298 '<b>Last Review</b>&nbsp;&nbsp;',
299 'Attendance:&nbsp;{attendance}&nbsp;&nbsp;',
300 'Attitude:&nbsp;{attitude}&nbsp;&nbsp;',
301 'Communication:&nbsp;{communication}&nbsp;&nbsp;',
302 'Excellence:&nbsp;{excellence}&nbsp;&nbsp;',
303 'Skills:&nbsp;{skills}&nbsp;&nbsp;',
304 'Teamwork:&nbsp;{teamwork}'
305 ],
306
307 height:90,
308 bodyPadding: 7,
309 // override initComponent to create and compile the template
310 // apply styles to the body of the panel
311 initComponent: function() {
312 this.tpl = new Ext.Template(this.tplMarkup);
313
314 // call the superclass's initComponent implementation
315 App.EmployeeDetail.superclass.initComponent.call(this);
316 }
317 });
318
319 Ext.define('App.ReviewWindow', {
320 extend: 'Ext.window.Window',
321
322 constructor: function(config) {
323 config = config || {};
324 Ext.apply(config, {
325 title:'Employee Performance Review',
326 width:320,
327 height:420,
328 layout:'fit',
329 items:[{
330 xtype:'form',
331 id:'employeereviewcomboform',
332 fieldDefaults: {
333 labelAlign: 'left',
334 labelWidth: 90,
335 anchor: '100%'
336 },
337 bodyPadding:5,
338 items:[{
339 xtype:'fieldset',
340 title:'Employee Info',
341 items:[{
342 xtype:'hiddenfield',
343 name:'employee_id'
344 },{
345 xtype:'textfield',
346 name:'first_name',
347 fieldLabel:'First Name',
348 allowBlank:false
349 },{
350 xtype:'textfield',
351 name:'last_name',
352 fieldLabel:'Last Name',
353 allowBlank:false
354 },{
355 xtype:'textfield',
356 name:'title',
357 fieldLabel:'Title',
358 allowBlank:false
359 }]
360 },{
361 xtype:'fieldset',
362 title:'Performance Review',
363 items:[{
364 xtype:'datefield',
365 name:'review_date',
366 fieldLabel:'Review Date',
367 format:'d-m-Y',
368 maxValue: new Date(),
369 value: new Date(),
370 allowBlank:false
371 },{
372 xtype:'slider',
373 name:'attendance',
374 fieldLabel:'Attendance',
375 value:5,
376 increment:1,
377 minValue:1,
378 maxValue:10
379 },{
380 xtype:'slider',
381 name:'attitude',
382 fieldLabel:'Attitude',
383 value:5,
384 minValue: 1,
385 maxValue: 10
386 },{
387 xtype:'slider',
388 name:'communication',
389 fieldLabel:'Communication',
390 value:5,
391 increment:1,
392 minValue:1,
393 maxValue:10
394 },{
395 xtype:'numberfield',
396 name:'excellence',
397 fieldLabel:'Excellence',
398 value:5,
399 minValue: 1,
400 maxValue: 10
401 },{
402 xtype:'numberfield',
403 name:'skills',
404 fieldLabel:'Skills',
405 value:5,
406 minValue: 1,
407 maxValue: 10
408 },{
409 xtype:'numberfield',
410 name:'teamwork',
411 fieldLabel:'Teamwork',
412 value:5,
413 minValue: 1,
414 maxValue: 10
415 }]
416 }]
417 }],
418 buttons:[{
419 text:'Cancel',
420 width:80,
421 handler:function() {
422 this.up('window').close();
423 }
424 },
425 {
426 text:'Save',
427 width:80,
428 handler:function(btn, eventObj) {
429 var window = btn.up('window');
430 var form = window.down('form').getForm();
431
432 if (form.isValid()) {
433 window.getEl().mask('saving data...');
434 var vals = form.getValues();
435 var employeeStore = Ext.data.StoreMgr.lookup('employeeStore');
436 var currentEmployee = employeeStore.findRecord('id', vals['employee_id']);
437
438 // look up id for this employee to see if they already exist
439 if(vals['employee_id'] && currentEmployee) {
440 currentEmployee.set('first_name', vals['first_name']);
441 currentEmployee.set('last_name', vals['last_name']);
442 currentEmployee.set('title', vals['title']);
443
444 var currentReview = Ext.data.StoreMgr.lookup('reviewStore').findRecord('employee_id', vals['employee_id']);
445 currentReview.set('review_date', vals['review_date']);
446 currentReview.set('attendance', vals['attendance']);
447 currentReview.set('attitude', vals['attitude']);
448 currentReview.set('communication', vals['communication']);
449 currentReview.set('excellence', vals['excellence']);
450 currentReview.set('skills', vals['skills']);
451 currentReview.set('teamwork', vals['teamwork']);
452 } else {
453 var newId = employeeStore.getCount() + 1;
454
455 employeeStore.add({
456 id: newId,
457 first_name: vals['first_name'],
458 last_name: vals['last_name'],
459 title: vals['title']
460 });
461
462 Ext.data.StoreMgr.lookup('reviewStore').add({
463 review_date: vals['review_date'],
464 attendance: vals['attendance'],
465 attitude: vals['attitude'],
466 communication: vals['communication'],
467 excellence: vals['excellence'],
468 skills: vals['skills'],
469 teamwork: vals['teamwork'],
470 employee_id: newId
471 });
472 }
473 window.getEl().unmask();
474 window.close();
475 }
476 }
477 }]
478 }); // end Ext.apply
479
480 App.ReviewWindow.superclass.constructor.call(this, config);
481
482 } // end constructor
483
484 });
485
486
487 // adds a record to the radar chart store and
488 // creates a series in the chart for selected employees
489 function refreshRadarChart(employees) {
490 employees = employees || []; // in case its called with nothing we'll at least have an empty array
491 var existingRadarChart = Ext.getCmp('radarchart'); // grab the radar chart component (used down below)
492 var reportsPanel = Ext.getCmp('reportspanel'); // grab the reports panel component (used down below)
493 var dynamicSeries = []; // setup an array of chart series that we'll create dynamically
494
495 for(var index = 0; index < employees.length; index++) {
496 var fullName = employees[index].get('first_name') + ' ' + employees[index].get('last_name');
497 var eid = 'eid_' + employees[index].get('id');
498
499 // add to the dynamic series we're building
500 dynamicSeries.push({
501 type: 'radar',
502 title: fullName,
503 xField: 'metric',
504 yField: eid,
505 labelDisplay: 'over',
506 showInLegend: true,
507 showMarkers: true,
508 markerCfg: {
509 radius: 5,
510 size: 5
511 },
512 style: {
513 'stroke-width': 2,
514 fill: 'none'
515 }
516 });
517
518 } // end for loop
519
520 // destroy the existing chart
521 existingRadarChart.destroy();
522 // create the new chart using the dynamic series we just made
523 var newRadarChart = new App.PerformanceRadar({series:dynamicSeries});
524 // mask the panel while we switch out charts
525 reportsPanel.getEl().mask('updating chart...');
526 // display the new one
527 reportsPanel.add(newRadarChart);
528 // un mask the reports panel
529 reportsPanel.getEl().unmask();
530 }
531
532 function refreshEmployeeDetails(employees) {
533 var detailsPanel = Ext.getCmp('detailspanel');
534 var reviewStore = Ext.data.StoreMgr.lookup('reviewStore');
535 var items = [];
536
537 for(var index = 0; index < employees.length; index++) {
538 var templateData = Ext.applyIf(employees[index].data, reviewStore.findRecord('employee_id', employees[index].get('id')).data);
539 var employeePanel = new App.EmployeeDetail({
540 title:employees[index].get('first_name') + ' ' + employees[index].get('last_name'),
541 data:templateData // combined employee and latest review dataTransfer
542 });
543 items.push(employeePanel);
544 }
545
546 detailsPanel.getEl().mask('updating details...');
547 detailsPanel.removeAll();
548 detailsPanel.add(items);
549 detailsPanel.getEl().unmask();
550 }
551
552 // sets Up Checkbox Selection Model for the Employee Grid
553 var checkboxSelModel = new Ext.selection.CheckboxModel();
554
555 var viewport = new Ext.container.Viewport({
556 id:'mainviewport',
557 layout: 'border', // sets up Ext.layout.container.Border
558 items: [{
559 xtype:'panel',
560 region:'center',
561 layout:'auto',
562 scrollable:true,
563 title:'Employee Performance Manager',
564 tbar:[{
565 text:'Add Employee',
566 tooltip:'Add a new employee',
567 iconCls:'add',
568 handler:function() { // display a window to add a new employee
569 new App.ReviewWindow().show();
570 }
571 }],
572 items:[{
573 xtype:'grid',
574 store:Ext.data.StoreMgr.lookup('employeeStore'),
575 height:300,
576 columns:[{
577 text:'First Name',
578 dataIndex:'first_name',
579 flex:2
580 },
581 {
582 text:'Last Name',
583 dataIndex:'last_name',
584 flex:2
585 },
586 {
587 text:'Title',
588 dataIndex:'title',
589 flex:3
590 },
591 {
592 xtype:'actioncolumn',
593 width:45,
594 items:[{
595 icon:'images/edit.png',
596 tooltip:'Edit Employee',
597 handler:function(grid, rowIndex, colIndex) {
598 var employee = grid.getStore().getAt(rowIndex);
599 var review = reviewStore.findRecord('employee_id', employee.get('id'));
600 var win = new App.ReviewWindow({hidden:true});
601 var form = win.down('form').getForm();
602 form.loadRecord(employee);
603 form.loadRecord(review);
604 win.show();
605 }
606 },
607 {
608 icon:'images/delete.png',
609 tooltip:'Delete Employee',
610 width:75,
611 handler:function(grid, rowIndex, colIndex) {
612 Ext.Msg.confirm('Remove Employee?', 'Are you sure you want to remove this employee?',
613 function(choice) {
614 if(choice === 'yes') {
615 var reviewStore = Ext.data.StoreMgr.lookup('reviewStore');
616
617 var employee = grid.getStore().getAt(rowIndex);
618 var reviewIndex = reviewStore.find('employee_id', employee.get('id'));
619 reviewStore.removeAt(reviewIndex);
620 grid.getStore().removeAt(rowIndex);
621 }
622 }
623 );
624 }
625 }]
626 }],
627 selModel: new Ext.selection.CheckboxModel(),
628 columnLines: true,
629 viewConfig: {stripeRows:true},
630 listeners:{
631 selectionchange:function(selModel, selected) {
632 refreshRadarChart(selected);
633 refreshEmployeeDetails(selected);
634 }
635 }
636 },{
637 xtype:'container',
638 id:'detailspanel',
639 layout:{
640 type:'vbox',
641 align:'stretch',
642 autoSize:true
643 }
644 }]
645 },{
646 xtype:'panel', // sets up the chart panel (starts collapsed)
647 region:'east',
648 id:'reportspanel',
649 title:'Performance Report',
650 width:350,
651 layout: 'fit',
652 items:[{
653 xtype:'performanceradar' // this instantiates a App.PerformanceRadar object
654 }]
655 }] // mainviewport items array ends here
656 });
657
658 });