Upgrade to ExtJS 3.2.1 - Released 04/27/2010
[extjs.git] / examples / tasks / tasks.js
1 /*!
2  * Ext JS Library 3.2.1
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 Ext.onReady(function(){
8     Ext.QuickTips.init();
9
10     var xg = Ext.grid;
11     // turn off default shadows which look funky in air
12     xg.GridEditor.prototype.shadow = false;
13     
14     var conn = Ext.data.SqlDB.getInstance();
15         conn.open('tasks.db');
16     
17     // the main grid store
18     var taskStore = new TaskStore(conn);
19     
20     // Category store shared by category combos
21     var catStore = new CategoryStore();
22     
23         taskStore.load({
24                 callback: function(){
25                         // first time?
26                         if(taskStore.getCount() < 1){
27                                 Ext.Msg.confirm('Create Tasks?', 'Your database is currently empty. Would you like to insert some demo data?', 
28                                         function(btn){
29                                                 if(btn == 'yes'){
30                                                         loadDemoTasks(taskStore);       
31                                                 }
32                                                 catStore.init(taskStore);
33                                         });
34                         }else{
35                                 catStore.init(taskStore);
36                         }
37                 }
38         });
39
40     // custom event to notify when a new category is available
41     taskStore.on('newcategory', catStore.addCategory, catStore);
42
43     // set of event handlers shared by combos to allow them to share
44     // the same local store
45     var comboEvents = {
46         focus: function(){
47             this.bindStore(catStore);
48         },
49         blur: function(c){
50             catStore.purgeListeners();
51         }
52     }
53
54     var completeColumn = new CompleteColumn();
55
56     // custom template for the grid header
57     var headerTpl = new Ext.Template(
58         '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
59         '<thead><tr class="x-grid3-hd-row">{cells}</tr></thead>',
60         '<tbody><tr class="new-task-row">',
61             '<td><div id="new-task-icon"></div></td>',
62             '<td><div class="x-small-editor" id="new-task-title"></div></td>',
63             '<td><div class="x-small-editor" id="new-task-cat"></div></td>',
64             '<td><div class="x-small-editor" id="new-task-due"></div></td>',
65         '</tr></tbody>',
66         "</table>"
67     );
68
69     var selections = new Ext.grid.RowSelectionModel();
70
71     // The main grid in all its configuration option glory
72     var grid = new xg.EditorGridPanel({
73         id:'tasks-grid',
74         store: taskStore,
75         sm: selections,
76         clicksToEdit: 'auto',
77         enableColumnHide:false,
78         enableColumnMove:false,
79                 border:false,
80                 title:'All Tasks',
81                 iconCls:'icon-show-all',
82                 region:'center',
83                 
84         plugins: completeColumn,
85
86         columns: [
87             completeColumn,
88             {
89                 header: "Task",
90                 width:400,
91                 sortable: true,
92                 dataIndex: 'title',
93                 id:'task-title',
94                 editor: new Ext.form.TextField({
95                     allowBlank: false
96                 })
97             },
98             {
99                 header: "Category",
100                 width:150,
101                 sortable: true,
102                 dataIndex: 'category',
103                 editor: new Ext.form.ComboBox({
104                     displayField: 'text',
105                     triggerAction: 'all',
106                     mode:'local',
107                     selectOnFocus:true,
108                     listClass:'x-combo-list-small',
109                     listeners: comboEvents
110                 })
111             },
112             {
113                 header: "Due Date",
114                 width: 150,
115                 sortable: true,
116                 renderer: Ext.util.Format.dateRenderer('D m/d/Y'),
117                 dataIndex: 'dueDate',
118                 groupRenderer: textDate(),
119                 groupName: 'Due',
120                 editor: new Ext.form.DateField({
121                     format : "m/d/Y"
122                 })
123             }
124         ],
125
126         view: new Ext.grid.GroupingView({
127             forceFit:true,
128             ignoreAdd: true,
129             emptyText: 'No Tasks to display',
130
131             templates: {
132                 header: headerTpl
133             },
134
135             getRowClass : function(r){
136                 var d = r.data;
137                 if(d.completed){
138                     return 'task-completed';
139                 }
140                 if(d.dueDate && d.dueDate.getTime() < new Date().clearTime().getTime()){
141                     return 'task-overdue';
142                 }
143                 return '';
144             }
145         })
146     });
147
148     var viewPanel = new Ext.Panel({
149         frame:true,
150         title: 'Views',
151         collapsible:true,
152         contentEl:'task-views',
153         titleCollapse: true
154     });
155     
156     var taskActions = new Ext.Panel({
157         frame:true,
158         title: 'Task Actions',
159         collapsible:true,
160         contentEl:'task-actions',
161         titleCollapse: true
162     });
163     
164     var groupActions = new Ext.Panel({
165         frame:true,
166         title: 'Task Grouping',
167         collapsible:true,
168         contentEl:'task-grouping',
169         titleCollapse: true
170     });
171     
172     var actionPanel = new Ext.Panel({
173         id:'action-panel',
174         region:'west',
175         split:true,
176         collapsible: true,
177         collapseMode: 'mini',
178         header: false,
179         width:200,
180         minWidth: 150,
181         border: false,
182         baseCls:'x-plain',
183         items: [taskActions, viewPanel, groupActions]
184     });
185
186     if(Ext.isAir){ // create AIR window
187         var win = new Ext.air.MainWindow({
188             layout:'border',
189             items: [actionPanel, grid],
190             title: 'Simple Tasks',
191             iconCls: 'icon-show-all'
192         }).render();
193         }else{
194         var viewport = new Ext.Viewport({
195             layout:'border',
196             items: [actionPanel, grid]
197         });
198     }
199
200     var ab = actionPanel.body;
201     ab.on('mousedown', doAction, null, {delegate:'a'});
202         ab.on('click', Ext.emptyFn, null, {delegate:'a', preventDefault:true});
203
204     grid.on('resize', syncFields);
205         grid.on('columnresize', syncFields);
206
207     grid.on('afteredit', function(e){
208         if(e.field == 'category'){
209             catStore.addCategory(e.value);
210         }
211         if(e.field == taskStore.getGroupState()){
212             taskStore.applyGrouping();
213         }
214
215     });
216
217     grid.on('keydown', function(e){
218          if(e.getKey() == e.DELETE && !grid.editing){
219              actions['action-delete']();
220          }
221     });
222
223     selections.on('selectionchange', function(sm){
224         var bd = taskActions.body, c = sm.getCount();
225         bd.select('li:not(#new-task)').setDisplayed(c > 0);
226         bd.select('span.s').setDisplayed(c > 1);
227     });
228
229     // The fields in the grid's header
230     var ntTitle = new Ext.form.TextField({
231         renderTo: 'new-task-title',
232         emptyText: 'Add a task...'
233     });
234
235     var ntCat = new Ext.form.ComboBox({
236         renderTo: 'new-task-cat',
237         disabled:true,
238         displayField: 'text',
239         triggerAction: 'all',
240         mode:'local',
241         selectOnFocus:true,
242         listClass:'x-combo-list-small',
243         listeners: comboEvents
244     });
245
246     var ntDue = new Ext.form.DateField({
247         renderTo: 'new-task-due',
248         value: new Date(),
249         disabled:true,
250         format : "m/d/Y"
251     });
252
253     // syncs the header fields' widths with the grid column widths
254     function syncFields(){
255         var cm = grid.getColumnModel();
256         ntTitle.setSize(cm.getColumnWidth(1)-2);
257         ntCat.setSize(cm.getColumnWidth(2)-4);
258         ntDue.setSize(cm.getColumnWidth(3)-4);
259     }
260     syncFields();
261
262     var editing = false, focused = false, userTriggered = false;
263     var handlers = {
264         focus: function(){
265             focused = true;
266         },
267         blur: function(){
268             focused = false;
269             doBlur.defer(250);
270         },
271         specialkey: function(f, e){
272             if(e.getKey()==e.ENTER){
273                 userTriggered = true;
274                 e.stopEvent();
275                 f.el.blur();
276                 if(f.triggerBlur){
277                     f.triggerBlur();
278                 }
279             }
280         }
281     }
282     ntTitle.on(handlers);
283     ntCat.on(handlers);
284     ntDue.on(handlers);
285
286     ntTitle.on('focus', function(){
287         focused = true;
288         if(!editing){
289             ntCat.enable();
290             ntDue.enable();
291             syncFields();
292             editing = true;
293         }
294     });
295
296     // when a field in the add bar is blurred, this determines
297     // whether a new task should be created
298     function doBlur(){
299         if(editing && !focused){
300             var title = ntTitle.getValue();
301             if(!Ext.isEmpty(title)){
302                 taskStore.addTask({
303                     taskId: Task.nextId(),
304                     title: title,
305                     dueDate: ntDue.getValue()||'',
306                     description: '', // ???
307                     category: ntCat.getValue(),
308                     completed: false
309                 });
310                 ntTitle.setValue('');
311                 if(userTriggered){ // if the entered to add the task, then go to a new add automatically
312                     userTriggered = false;
313                     ntTitle.focus.defer(100, ntTitle);
314                 }
315             }
316             ntCat.disable();
317             ntDue.disable();
318             editing = false;
319         }
320     }
321         
322     var actions = {
323         'view-all' : function(){
324                 taskStore.applyFilter('all');
325                 grid.setTitle('All Tasks', 'icon-show-all');
326         },
327         
328         'view-active' : function(){
329                 taskStore.applyFilter(false);
330                 grid.setTitle('Active Tasks', 'icon-show-active');
331         },
332         
333         'view-complete' : function(){
334                 taskStore.applyFilter(true);
335                 grid.setTitle('Completed Tasks', 'icon-show-complete');
336         },
337         
338         'action-new' : function(){
339                 ntTitle.focus();
340         },
341         
342         'action-complete' : function(){
343                 selections.each(function(s){
344                         s.set('completed', true);
345                 });
346             taskStore.applyFilter();
347         },
348         
349         'action-active' : function(){
350                 selections.each(function(s){
351                         s.set('completed', false);
352                 });
353             taskStore.applyFilter();
354         },
355         
356         'action-delete' : function(){
357                 Ext.Msg.confirm('Confirm', 'Are you sure you want to delete the selected task(s)?', 
358                 function(btn){
359                 if(btn == 'yes'){
360                         selections.each(function(s){
361                                         taskStore.remove(s);
362                                 });
363                 }
364             });
365         },
366         
367         'group-date' : function(){
368                 taskStore.groupBy('dueDate');
369         },
370         
371         'group-cat' : function(){
372                 taskStore.groupBy('category');
373         },
374         
375         'no-group' : function(){
376                 taskStore.clearGrouping();
377         }
378     };
379     
380     function doAction(e, t){
381         e.stopEvent();
382         actions[t.id]();
383     }
384     
385     
386     // generates a renderer function to be used for textual date groups
387     function textDate(){
388         // create the cache of ranges to be reused
389         var today = new Date().clearTime(true);
390         var year = today.getFullYear();
391         var todayTime = today.getTime();
392         var yesterday = today.add('d', -1).getTime();
393         var tomorrow = today.add('d', 1).getTime();
394         var weekDays = today.add('d', 6).getTime();
395         var lastWeekDays = today.add('d', -6).getTime();
396
397         return function(date){
398             if(!date) {
399                 return '(No Date)';
400             }
401             var notime = date.clearTime(true).getTime();
402
403             if (notime == todayTime) {
404                 return 'Today';
405             }
406             if(notime > todayTime){
407                 if (notime == tomorrow) {
408                     return 'Tomorrow';
409                 }
410                 if (notime <= weekDays) {
411                     return date.format('l');
412                 }
413             }else {
414                 if(notime == yesterday) {
415                         return 'Yesterday';
416                     }
417                     if(notime >= lastWeekDays) {
418                         return 'Last ' + date.format('l');
419                     }
420             }            
421             return date.getFullYear() == year ? date.format('D m/d') : date.format('D m/d/Y');
422        }
423     }
424 });
425
426 /* This is used to laod some demo tasks if the task database is empty */
427 function loadDemoTasks(store){
428         var s = new Date();
429         // hardcoded demo tasks
430         store.addTask({taskId: Task.nextId(), title:'Start documentation of Ext 2.0', category:'Ext', description:'', dueDate: s.add('d', 21), completed: false});
431         store.addTask({taskId: Task.nextId(), title:'Release Ext 1.l Beta 2', category:'Ext', description:'', dueDate:s.add('d', 2), completed: false});
432         store.addTask({taskId: Task.nextId(), title:'Take wife to see movie', category:'Family', description:'', dueDate:s.add('d', 2), completed: false});
433         store.addTask({taskId: Task.nextId(), title:'Finish task list demo app', category:'Ext', description:'', dueDate:s.add('d', 2), completed: false});
434         store.addTask({taskId: Task.nextId(), title:'Do something other than work', category:'Family', description:'', dueDate:s.add('d', -1), completed: false});
435         store.addTask({taskId: Task.nextId(), title:'Go to the grocery store', category:'Family', description:'', dueDate:s.add('d', -1), completed: true});
436         store.addTask({taskId: Task.nextId(), title:'Reboot my computer', category:'Misc', description:'', dueDate:s, completed: false});
437         store.addTask({taskId: Task.nextId(), title:'Respond to emails', category:'Ext', description:'', dueDate:s, completed: true});
438 }
439
440     
441