]> git.proxmox.com Git - extjs.git/blame - extjs/examples/classic/simple-tasks/app/controller/Lists.js
add extjs 6.0.1 sources
[extjs.git] / extjs / examples / classic / simple-tasks / app / controller / Lists.js
CommitLineData
6527f429
DM
1/**\r
2 * @class SimpleTasks.controller.Lists\r
3 * @extends Ext.app.Controller\r
4 */\r
5Ext.define('SimpleTasks.controller.Lists', {\r
6 extend: 'Ext.app.Controller',\r
7\r
8 models: ['List'],\r
9 stores: ['Lists', 'Tasks'],\r
10\r
11 views: [\r
12 'lists.Tree',\r
13 'lists.ContextMenu',\r
14 'Toolbar'\r
15 ],\r
16\r
17 refs: [\r
18 {\r
19 ref: 'listTree',\r
20 selector: 'listTree'\r
21 },\r
22 {\r
23 ref: 'taskGrid',\r
24 selector: 'taskGrid'\r
25 },\r
26 {\r
27 ref: 'taskForm',\r
28 selector: 'taskForm'\r
29 },\r
30 {\r
31 ref: 'contextMenu',\r
32 selector: 'listsContextMenu',\r
33 xtype: 'listsContextMenu',\r
34 autoCreate: true\r
35 }\r
36 ],\r
37\r
38 init: function() {\r
39 var me = this,\r
40 listsStore = me.getListsStore();\r
41\r
42 me.control({\r
43 '[iconCls=tasks-new-list]': {\r
44 click: me.handleNewListClick\r
45 },\r
46 '[iconCls=tasks-new-folder]': {\r
47 click: me.handleNewFolderClick\r
48 },\r
49 '[iconCls=tasks-delete-list]': {\r
50 click: me.handleDeleteClick\r
51 },\r
52 '[iconCls=tasks-delete-folder]': {\r
53 click: me.handleDeleteClick\r
54 },\r
55 'listTree': {\r
56 afterrender: me.handleAfterListTreeRender,\r
57 edit: me.updateList,\r
58 completeedit: me.handleCompleteEdit,\r
59 canceledit: me.handleCancelEdit,\r
60 deleteclick: me.handleDeleteIconClick,\r
61 selectionchange: me.filterTaskGrid,\r
62 taskdrop: me.updateTaskList,\r
63 listdrop: me.reorderList,\r
64 itemmouseenter: me.showActions,\r
65 itemmouseleave: me.hideActions,\r
66 itemcontextmenu: me.showContextMenu\r
67 }\r
68 });\r
69\r
70 if(listsStore.isLoading()) {\r
71 listsStore.on('load', me.handleListsLoad, me);\r
72 } else {\r
73 me.handleListsLoad(listsStore);\r
74 }\r
75 listsStore.on('write', me.syncListsStores, me, {\r
76 buffer: 1\r
77 });\r
78 },\r
79\r
80 /**\r
81 * Handles a click on the "New List" button or context menu item.\r
82 * @param {Ext.Component} component\r
83 * @param {Ext.EventObject} e\r
84 */\r
85 handleNewListClick: function(component, e) {\r
86 this.addList(true);\r
87 },\r
88\r
89 /**\r
90 * Handles a click on the "New Folder" button or context menu item.\r
91 * @param {Ext.Component} component\r
92 * @param {Ext.EventObject} e\r
93 */\r
94 handleNewFolderClick: function(component, e) {\r
95 this.addList();\r
96 },\r
97\r
98 /**\r
99 * Adds an empty list to the lists store and starts editing the new list\r
100 * @param {Boolean} leaf True if the new node should be a leaf node.\r
101 */\r
102 addList: function(leaf) {\r
103 var me = this,\r
104 listTree = me.getListTree(),\r
105 cellEditingPlugin = listTree.cellEditingPlugin,\r
106 selectionModel = listTree.getSelectionModel(),\r
107 selectedList = selectionModel.getSelection()[0],\r
108 parentList = selectedList.isLeaf() ? selectedList.parentNode : selectedList,\r
109 newList = Ext.create('SimpleTasks.model.List', {\r
110 name: 'New ' + (leaf ? 'List' : 'Folder'),\r
111 leaf: leaf,\r
112 loaded: true // set loaded to true, so the tree won't try to dynamically load children for this node when expanded\r
113 }),\r
114 expandAndEdit = function() {\r
115 if(parentList.isExpanded()) {\r
116 selectionModel.select(newList);\r
117 me.addedNode = newList;\r
118 cellEditingPlugin.startEdit(newList, 0);\r
119 } else {\r
120 listTree.on('afteritemexpand', function startEdit(list) {\r
121 if(list === parentList) {\r
122 selectionModel.select(newList);\r
123 me.addedNode = newList;\r
124 cellEditingPlugin.startEdit(newList, 0);\r
125 // remove the afterexpand event listener\r
126 listTree.un('afteritemexpand', startEdit);\r
127 }\r
128 });\r
129 parentList.expand();\r
130 }\r
131 };\r
132 \r
133 parentList.appendChild(newList);\r
134 listTree.getStore().sync();\r
135 if(listTree.getView().isVisible(true)) {\r
136 expandAndEdit();\r
137 } else {\r
138 listTree.on('expand', function onExpand() {\r
139 expandAndEdit();\r
140 listTree.un('expand', onExpand);\r
141 });\r
142 listTree.expand();\r
143 }\r
144 },\r
145\r
146 /**\r
147 * Handles the list list's "edit" event.\r
148 * Updates the list on the server whenever a list record is updated using the tree editor.\r
149 * @param {Ext.grid.plugin.CellEditing} editor\r
150 * @param {Object} e an edit event object\r
151 */\r
152 updateList: function(editor, e) {\r
153 var me = this,\r
154 list = e.record;\r
155\r
156 list.save({\r
157 success: function(list, operation) {\r
158 // filter the task list by the currently selected list. This is necessary for newly added lists\r
159 // since this is the first point at which we have a primary key "id" from the server.\r
160 // If we don't filter here then any new tasks that are added will not appear until the filter is triggered by a selection change.\r
161 me.filterTaskGrid(me.getListTree().getSelectionModel(), [list]);\r
162 },\r
163 failure: function(list, operation) {\r
164 var error = operation.getError(),\r
165 msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;\r
166\r
167 Ext.MessageBox.show({\r
168 title: 'Update List Failed',\r
169 msg: msg,\r
170 icon: Ext.Msg.ERROR,\r
171 buttons: Ext.Msg.OK\r
172 });\r
173 }\r
174 });\r
175 },\r
176 \r
177 /**\r
178 * Handles the list tree's complete edit event\r
179 * @param {Ext.grid.plugin.CellEditing} editor\r
180 * @param {Object} e an edit event object\r
181 */\r
182 handleCompleteEdit: function(editor, e){\r
183 delete this.addedNode;\r
184 },\r
185\r
186 /**\r
187 * Handles the list tree's cancel edit event\r
188 * removes a newly added node if editing is cancelled before the node has been saved to the server\r
189 * @param {Ext.grid.plugin.CellEditing} editor\r
190 * @param {Object} e an edit event object\r
191 */\r
192 handleCancelEdit: function(editor, e) {\r
193 var list = e.record,\r
194 parent = list.parentNode,\r
195 added = this.addedNode;\r
196\r
197 delete this.addedNode;\r
198 if (added === list) {\r
199 // Only remove it if it's been newly added\r
200 parent.removeChild(list);\r
201 this.getListTree().getStore().sync();\r
202 this.getListTree().getSelectionModel().select([parent]);\r
203 }\r
204 },\r
205\r
206 /**\r
207 * Handles a click on a delete icon in the list tree.\r
208 * @param {Ext.tree.View} view\r
209 * @param {Number} rowIndex\r
210 * @param {Number} colIndex\r
211 * @param {Ext.grid.column.Action} column\r
212 * @param {EventObject} e\r
213 */\r
214 handleDeleteIconClick: function(view, rowIndex, colIndex, column, e) {\r
215 this.deleteList(view.getRecord(view.findTargetByEvent(e)));\r
216 },\r
217\r
218 /**\r
219 * Handles a click on the "Delete List" or "Delete Folder" button or menu item\r
220 * @param {Ext.Component} component\r
221 * @param {Ext.EventObject} e\r
222 */\r
223 handleDeleteClick: function(component, e) {\r
224 this.deleteList(this.getListTree().getSelectionModel().getSelection()[0]);\r
225 },\r
226\r
227 /**\r
228 * Deletes a list from the server and updates the view.\r
229 * @param {SimpleTasks.model.List} list\r
230 */\r
231 deleteList: function(list) {\r
232 var me = this,\r
233 listTree = me.getListTree(),\r
234 listName = list.get('name'),\r
235 selModel = listTree.getSelectionModel(),\r
236 tasksStore = me.getTasksStore(),\r
237 listsStore = me.getListsStore(),\r
238 isLocal = SimpleTasks.Settings.useLocalStorage,\r
239 tasks;\r
240\r
241 Ext.Msg.show({\r
242 title: 'Delete List?',\r
243 msg: 'Are you sure you want to permanently delete the "' + listName + '" list and all its tasks?',\r
244 buttons: Ext.Msg.YESNO,\r
245 fn: function(response) {\r
246 if(response === 'yes') {\r
247 // recursively remove any tasks from the store that are associated with the list being deleted or any of its children.\r
248 (function deleteTasks(list) {\r
249 tasks = tasksStore.queryBy(function(task, id) {\r
250 return task.get('list_id') === list.get('id');\r
251 });\r
252 tasksStore.remove(tasks.getRange(0, tasks.getCount()), !isLocal);\r
253\r
254 list.eachChild(function(child) {\r
255 deleteTasks(child);\r
256 });\r
257 })(list);\r
258\r
259 // destroy the tree node on the server\r
260 list.parentNode.removeChild(list);\r
261 listsStore.sync({\r
262 failure: function(batch, options) {\r
263 var error = batch.exceptions[0].getError(),\r
264 msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;\r
265\r
266 Ext.MessageBox.show({\r
267 title: 'Delete List Failed',\r
268 msg: msg,\r
269 icon: Ext.Msg.ERROR,\r
270 buttons: Ext.Msg.OK\r
271 });\r
272 }\r
273 });\r
274\r
275 if(isLocal) {\r
276 // only need to sync the tasks store when using local storage.\r
277 // when using an ajax proxy we will allow the server to handle deleting any tasks associated with the deleted list(s)\r
278 tasksStore.sync();\r
279 }\r
280\r
281 // If there is no selection, or the selection no longer exists in the store (it was part of the deleted node(s))\r
282 // then select the "All Lists" root\r
283 if (!selModel.hasSelection() || !listsStore.getNodeById(selModel.getSelection()[0].getId())) {\r
284 selModel.select(0);\r
285 }\r
286 \r
287 // refresh the list view so the task counts will be accurate\r
288 listTree.refreshView();\r
289 }\r
290 }\r
291 });\r
292\r
293 },\r
294\r
295 /**\r
296 * Handles the list tree's "selectionchange" event.\r
297 * Filters the task store based on the selected list.\r
298 * @param {Ext.selection.RowModel} selModel\r
299 * @param {SimpleTasks.model.List[]} lists\r
300 */\r
301 filterTaskGrid: function(selModel, lists) {\r
302 if (lists.length === 0) {\r
303 return;\r
304 }\r
305 \r
306 var list = lists[0],\r
307 tasksStore = this.getTasksStore(),\r
308 listIds = [],\r
309 deleteListBtn = Ext.getCmp('delete-list-btn'),\r
310 deleteFolderBtn = Ext.getCmp('delete-folder-btn'),\r
311 i = 0;\r
312\r
313 // build an array of all the list_id's in the hierarchy of the selected list\r
314 list.cascadeBy(function(list) {\r
315 listIds.push(list.get('id'));\r
316 });\r
317\r
318 tasksStore.addFilter({\r
319 property: "list_id",\r
320 value: new RegExp('^' + listIds.join('$|^') + '$')\r
321 });\r
322\r
323 // set the center panel's title to the name of the currently selected list\r
324 this.getTaskGrid().setTitle(list.get('name'));\r
325\r
326 // enable or disable the "delete list" and "delete folder" buttons depending on what type of node is selected\r
327 if(list.get('id') === -1) {\r
328 deleteListBtn.disable();\r
329 deleteFolderBtn.disable();\r
330 } else if(list.isLeaf()) {\r
331 deleteListBtn.enable();\r
332 deleteFolderBtn.disable();\r
333 } else {\r
334 deleteListBtn.disable();\r
335 deleteFolderBtn.enable();\r
336 }\r
337\r
338 // make the currently selected list the default value for the list field on the new task form\r
339 this.getTaskForm().query('[name=list_id]')[0].setValue(list.get('id'));\r
340 },\r
341\r
342 /**\r
343 * Handles the list view's "taskdrop" event. Runs when a task is dragged and dropped on a list.\r
344 * Updates the task to belong to the list it was dropped on.\r
345 * @param {SimpleTasks.model.Task} task The Task record that was dropped\r
346 * @param {SimpleTasks.model.List} list The List record that the mouse was over when the drop happened\r
347 */\r
348 updateTaskList: function(task, list) {\r
349 var me = this,\r
350 listId = list.get('id');\r
351\r
352 // set the tasks list_id field to the id of the list it was dropped on\r
353 task.set('list_id', listId);\r
354 // save the task to the server\r
355 task.save({\r
356 success: function(task, operation) {\r
357 // refresh the lists view so the task counts will be updated.\r
358 me.getListTree().refreshView();\r
359 },\r
360 failure: function(task, operation) {\r
361 var error = operation.getError(),\r
362 msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;\r
363\r
364 Ext.MessageBox.show({\r
365 title: 'Move Task Failed',\r
366 msg: msg,\r
367 icon: Ext.Msg.ERROR,\r
368 buttons: Ext.Msg.OK\r
369 });\r
370 }\r
371 });\r
372 },\r
373\r
374 /**\r
375 * Handles the list view's "listdrop" event. Runs after a list is reordered by dragging and dropping.\r
376 * Commits the lists new position in the tree to the server.\r
377 * @param {SimpleTasks.model.List} list The List that was dropped\r
378 * @param {SimpleTasks.model.List} overList The List that the List was dropped on\r
379 * @param {String} position `"before"` or `"after"` depending on whether the mouse is above or below the midline of the node.\r
380 */\r
381 reorderList: function(list, overList, position) {\r
382 var listsStore = this.getListsStore();\r
383\r
384 if(SimpleTasks.Settings.useLocalStorage) {\r
385 listsStore.sync();\r
386 } else {\r
387 Ext.Ajax.request({\r
388 url: 'php/list/move.php',\r
389 jsonData: {\r
390 id: list.get('id'),\r
391 relatedId: overList.get('id'),\r
392 position: position\r
393 },\r
394 success: function(response, options) {\r
395 var responseData = Ext.decode(response.responseText);\r
396\r
397 if(!responseData.success) {\r
398 Ext.MessageBox.show({\r
399 title: 'Move Task Failed',\r
400 msg: responseData.message,\r
401 icon: Ext.Msg.ERROR,\r
402 buttons: Ext.Msg.OK\r
403 });\r
404 }\r
405 },\r
406 failure: function(response, options) {\r
407 Ext.MessageBox.show({\r
408 title: 'Move Task Failed',\r
409 msg: response.status + ' ' + response.statusText,\r
410 icon: Ext.Msg.ERROR,\r
411 buttons: Ext.Msg.OK\r
412 });\r
413 }\r
414 });\r
415 }\r
416 \r
417 // refresh the lists view so the task counts will be updated.\r
418 this.getListTree().refreshView();\r
419 },\r
420\r
421 /**\r
422 * Handles the initial tasks store "load" event,\r
423 * refreshes the List tree view then removes itself as a handler.\r
424 * @param {SimpleTasks.store.Tasks} tasksStore\r
425 * @param {SimpleTasks.model.Task[]} tasks\r
426 * @param {Boolean} success\r
427 * @param {Ext.data.Operation} operation\r
428 */\r
429 handleTasksLoad: function(tasksStore, tasks, success, operation) {\r
430 var me = this,\r
431 listTree = me.getListTree(),\r
432 selectionModel = listTree.getSelectionModel();\r
433\r
434 // refresh the lists view so the task counts will be updated.\r
435 listTree.refreshView();\r
436 // filter the task grid by the selected list\r
437 me.filterTaskGrid(selectionModel, selectionModel.getSelection());\r
438 // remove the event listener after the first run\r
439 tasksStore.un('load', this.handleTasksLoad, this);\r
440 },\r
441\r
442 /**\r
443 * Handles the initial lists store "load" event,\r
444 * selects the list tree's root node if the list tree exists, loads the tasks store, then removes itself as a handler.\r
445 * @param {SimpleTasks.store.Lists} listsStore\r
446 * @param {SimpleTasks.model.List[]} lists\r
447 * @param {Boolean} success\r
448 * @param {Ext.data.Operation} operation\r
449 */\r
450 handleListsLoad: function(listsStore, lists, success, operation) {\r
451 var me = this,\r
452 listTree = me.getListTree(),\r
453 tasksStore = me.getTasksStore();\r
454 \r
455 if(listTree) {\r
456 // if the list tree exists when the lists store is first loaded, select the root node.\r
457 // when using a server proxy, the list tree will always exist at this point since asyncronous loading of data allows time for the list tree to be created and rendered.\r
458 // when using a local storage proxy, the list tree will not yet exist at this point, so we'll have to select the root node on render instead (see handleAfterListTreeRender)\r
459 listTree.getSelectionModel().select(0);\r
460 }\r
461 // wait until lists are done loading to load tasks since the task grid's "list" column renderer depends on lists store being loaded\r
462 me.getTasksStore().load();\r
463 // if the tasks store is asynchronous (server proxy) attach load handler for refreshing the list counts after loading is complete\r
464 // if local storage is being used, isLoading will be false here since load() will run syncronously, so there is no need\r
465 // to refresh the lists view because load will have happened before the list tree is even rendered\r
466 if(tasksStore.isLoading()) {\r
467 tasksStore.on('load', me.handleTasksLoad, me);\r
468 }\r
469 // remove the event listener after the first run\r
470 listsStore.un('load', me.handleListsLoad, me);\r
471 },\r
472\r
473 /**\r
474 * Handles the list tree's "afterrender" event\r
475 * Selects the lists tree's root node, if the list tree exists\r
476 * @param {SimpleTasks.view.lists.Tree} listTree\r
477 */\r
478 handleAfterListTreeRender: function(listTree) {\r
479 listTree.getSelectionModel().select(0);\r
480 },\r
481\r
482 /**\r
483 * Handles the lists store's write event.\r
484 * Syncronizes the other read only list stores with the newly saved data\r
485 * @param {SimpleTasks.store.Lists} listsStore\r
486 * @param {Ext.data.Operation} operation\r
487 */\r
488 syncListsStores: function(listsStore, operation) {\r
489 var me = this,\r
490 stores = [\r
491 Ext.getStore('Lists-TaskGrid'),\r
492 Ext.getStore('Lists-TaskEditWindow'),\r
493 Ext.getStore('Lists-TaskForm')\r
494 ],\r
495 storesLen = stores.length,\r
496 records = operation.getRecords(),\r
497 recordsLen = records.length, \r
498 i, j, listToSync, node, list, store;\r
499 \r
500 for (i = 0; i < recordsLen; ++i) {\r
501 list = records[i];\r
502 for (j = 0; j < storesLen; ++j) {\r
503 store = stores[j];\r
504 if (store) {\r
505 listToSync = store.getNodeById(list.getId());\r
506 switch(operation.action) {\r
507 case 'create':\r
508 node = store.getNodeById(list.parentNode.getId()) || store.getRoot();\r
509 node.appendChild(list.copy(list.getId()));\r
510 break;\r
511 case 'update':\r
512 if(listToSync) {\r
513 listToSync.set(list.data);\r
514 listToSync.commit();\r
515 }\r
516 break;\r
517 case 'destroy':\r
518 if(listToSync) {\r
519 listToSync.remove(false);\r
520 }\r
521 }\r
522 }\r
523 }\r
524 }\r
525 },\r
526\r
527 /**\r
528 * Handles a mouseenter event on a list tree node.\r
529 * Shows the node's action icons.\r
530 * @param {Ext.tree.View} view\r
531 * @param {SimpleTasks.model.List} list\r
532 * @param {HTMLElement} node\r
533 * @param {Number} rowIndex\r
534 * @param {Ext.EventObject} e\r
535 */\r
536 showActions: function(view, list, node, rowIndex, e) {\r
537 var icons = Ext.fly(node).query('.x-action-col-icon');\r
538 if(view.getRecord(node).get('id') > 0) {\r
539 Ext.each(icons, function(icon){\r
540 Ext.get(icon).removeCls('x-hidden');\r
541 });\r
542 }\r
543 },\r
544\r
545 /**\r
546 * Handles a mouseleave event on a list tree node.\r
547 * Hides the node's action icons.\r
548 * @param {Ext.tree.View} view\r
549 * @param {SimpleTasks.model.List} list\r
550 * @param {HTMLElement} node\r
551 * @param {Number} rowIndex\r
552 * @param {Ext.EventObject} e\r
553 */\r
554 hideActions: function(view, list, node, rowIndex, e) {\r
555 var icons = Ext.fly(node).query('.x-action-col-icon');\r
556 Ext.each(icons, function(icon){\r
557 Ext.get(icon).addCls('x-hidden');\r
558 });\r
559 },\r
560\r
561 /**\r
562 * Handles the list tree's itemcontextmenu event\r
563 * Shows the list context menu.\r
564 * @param {Ext.grid.View} view\r
565 * @param {SimpleTasks.model.List} list\r
566 * @param {HTMLElement} node\r
567 * @param {Number} rowIndex\r
568 * @param {Ext.EventObject} e\r
569 */\r
570 showContextMenu: function(view, list, node, rowIndex, e) {\r
571 var contextMenu = this.getContextMenu(),\r
572 newListItem = Ext.getCmp('new-list-item'),\r
573 newFolderItem = Ext.getCmp('new-folder-item'),\r
574 deleteFolderItem = Ext.getCmp('delete-folder-item'),\r
575 deleteListItem = Ext.getCmp('delete-list-item');\r
576\r
577 if(list.isLeaf()) {\r
578 newListItem.hide();\r
579 newFolderItem.hide();\r
580 deleteFolderItem.hide();\r
581 deleteListItem.show();\r
582 } else {\r
583 newListItem.show();\r
584 newFolderItem.show();\r
585 if(list.isRoot()) {\r
586 deleteFolderItem.hide();\r
587 } else {\r
588 deleteFolderItem.show();\r
589 }\r
590 deleteListItem.hide();\r
591 }\r
592 contextMenu.setList(list);\r
593 contextMenu.showAt(e.getX(), e.getY());\r
594 e.preventDefault();\r
595 }\r
596\r
597});\r