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