123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- /**
- * @class SimpleTasks.controller.Lists
- * @extends Ext.app.Controller
- */
- Ext.define('SimpleTasks.controller.Lists', {
- extend: 'Ext.app.Controller',
- models: ['List'],
- stores: ['Lists', 'Tasks'],
- views: [
- 'lists.Tree',
- 'lists.ContextMenu',
- 'Toolbar'
- ],
- refs: [
- {
- ref: 'listTree',
- selector: 'listTree'
- },
- {
- ref: 'taskGrid',
- selector: 'taskGrid'
- },
- {
- ref: 'taskForm',
- selector: 'taskForm'
- },
- {
- ref: 'contextMenu',
- selector: 'listsContextMenu',
- xtype: 'listsContextMenu',
- autoCreate: true
- }
- ],
- init: function() {
- var me = this,
- listsStore = me.getListsStore(),
- tasksStore = me.getTasksStore();
- me.control({
- '[iconCls=tasks-new-list]': {
- click: me.handleNewListClick
- },
- '[iconCls=tasks-new-folder]': {
- click: me.handleNewFolderClick
- },
- '[iconCls=tasks-delete-list]': {
- click: me.handleDeleteClick
- },
- '[iconCls=tasks-delete-folder]': {
- click: me.handleDeleteClick
- },
- 'listTree': {
- afterrender: me.handleAfterListTreeRender,
- edit: me.updateList,
- canceledit: me.handleCancelEdit,
- deleteclick: me.handleDeleteIconClick,
- selectionchange: me.filterTaskGrid,
- taskdrop: me.updateTaskList,
- listdrop: me.reorderList,
- itemmouseenter: me.showActions,
- itemmouseleave: me.hideActions,
- itemcontextmenu: me.showContextMenu
- }
- });
- if(listsStore.isLoading()) {
- listsStore.on('load', me.handleListsLoad, me);
- } else {
- me.handleListsLoad(listsStore);
- }
- listsStore.on('write', me.syncListsStores, me);
- },
- /**
- * Handles a click on the "New List" button or context menu item.
- * @param {Ext.Component} component
- * @param {Ext.EventObject} e
- */
- handleNewListClick: function(component, e) {
- this.addList(true);
- },
- /**
- * Handles a click on the "New Folder" button or context menu item.
- * @param {Ext.Component} component
- * @param {Ext.EventObject} e
- */
- handleNewFolderClick: function(component, e) {
- this.addList();
- },
- /**
- * Adds an empty list to the lists store and starts editing the new list
- * @param {Boolean} leaf True if the new node should be a leaf node.
- */
- addList: function(leaf) {
- var listTree = this.getListTree(),
- cellEditingPlugin = listTree.cellEditingPlugin,
- selectionModel = listTree.getSelectionModel(),
- selectedList = selectionModel.getSelection()[0],
- parentList = selectedList.isLeaf() ? selectedList.parentNode : selectedList,
- newList = Ext.create('SimpleTasks.model.List', {
- name: 'New ' + (leaf ? 'List' : 'Folder'),
- leaf: leaf,
- loaded: true // set loaded to true, so the tree won't try to dynamically load children for this node when expanded
- }),
- expandAndEdit = function() {
- if(parentList.isExpanded()) {
- selectionModel.select(newList);
- cellEditingPlugin.startEdit(newList, 0);
- } else {
- listTree.on('afteritemexpand', function startEdit(list) {
- if(list === parentList) {
- selectionModel.select(newList);
- cellEditingPlugin.startEdit(newList, 0);
- // remove the afterexpand event listener
- listTree.un('afteritemexpand', startEdit);
- }
- });
- parentList.expand();
- }
- };
- parentList.appendChild(newList);
- if(listTree.getView().isVisible(true)) {
- expandAndEdit();
- } else {
- listTree.on('expand', function onExpand() {
- expandAndEdit();
- listTree.un('expand', onExpand);
- });
- listTree.expand();
- }
- },
- /**
- * Handles the list list's "edit" event.
- * Updates the list on the server whenever a list record is updated using the tree editor.
- * @param {Ext.grid.plugin.CellEditing} editor
- * @param {Object} e an edit event object
- */
- updateList: function(editor, e) {
- var me = this,
- list = e.record;
- list.save({
- success: function(list, operation) {
- // filter the task list by the currently selected list. This is necessary for newly added lists
- // since this is the first point at which we have a primary key "id" from the server.
- // 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.
- me.filterTaskGrid(me.getListTree().getSelectionModel(), [list]);
- },
- failure: function(list, operation) {
- var error = operation.getError(),
- msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;
- Ext.MessageBox.show({
- title: 'Update List Failed',
- msg: msg,
- icon: Ext.Msg.ERROR,
- buttons: Ext.Msg.OK
- });
- }
- });
- },
- /**
- * Handles the list tree's cancel edit event
- * removes a newly added node if editing is canceled before the node has been saved to the server
- * @param {Ext.grid.plugin.CellEditing} editor
- * @param {Object} e an edit event object
- */
- handleCancelEdit: function(editor, e) {
- var list = e.record,
- parent = list.parentNode;
- parent.removeChild(list);
- this.getListTree().getSelectionModel().select([parent]);
- },
- /**
- * Handles a click on a delete icon in the list tree.
- * @param {Ext.tree.View} view
- * @param {Number} rowIndex
- * @param {Number} colIndex
- * @param {Ext.grid.column.Action} column
- * @param {EventObject} e
- */
- handleDeleteIconClick: function(view, rowIndex, colIndex, column, e) {
- this.deleteList(view.getRecord(view.findTargetByEvent(e)));
- },
- /**
- * Handles a click on the "Delete List" or "Delete Folder" button or menu item
- * @param {Ext.Component} component
- * @param {Ext.EventObject} e
- */
- handleDeleteClick: function(component, e) {
- this.deleteList(this.getListTree().getSelectionModel().getSelection()[0]);
- },
- /**
- * Deletes a list from the server and updates the view.
- * @param {SimpleTasks.model.List} list
- */
- deleteList: function(list) {
- var me = this,
- listTree = me.getListTree(),
- listName = list.get('name'),
- selModel = listTree.getSelectionModel(),
- tasksStore = me.getTasksStore(),
- listsStore = me.getListsStore(),
- isLocal = this.getListsStore().getProxy().type === 'localstorage',
- filters, tasks;
- Ext.Msg.show({
- title: 'Delete List?',
- msg: 'Are you sure you want to permanently delete the "' + listName + '" list and all its tasks?',
- buttons: Ext.Msg.YESNO,
- fn: function(response) {
- if(response === 'yes') {
- // save the existing filters
- filters = tasksStore.filters.getRange(0, tasksStore.filters.getCount() - 1);
- // clear the filters in the tasks store, we need to do this because tasksStore.queryBy only queries based on the current filter,
- // but we need to query all lists in the store
- tasksStore.clearFilter();
- // recursively remove any tasks from the store that are associated with the list being deleted or any of its children.
- (function deleteTasks(list) {
- tasks = tasksStore.queryBy(function(task, id) {
- return task.get('list_id') === list.get('id');
- });
- tasksStore.remove(tasks.getRange(0, tasks.getCount() - 1), !isLocal);
- list.eachChild(function(child) {
- deleteTasks(child);
- });
- })(list);
- // reapply the filters
- tasksStore.filter(filters);
- // destroy the tree node on the server
- list.parentNode.removeChild(list);
- listsStore.sync({
- failure: function(batch, options) {
- var error = batch.exceptions[0].getError(),
- msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;
- Ext.MessageBox.show({
- title: 'Delete List Failed',
- msg: msg,
- icon: Ext.Msg.ERROR,
- buttons: Ext.Msg.OK
- });
- }
- });
- if(isLocal) {
- // only need to sync the tasks store when using local storage.
- // when using an ajax proxy we will allow the server to handle deleting any tasks associated with the deleted list(s)
- tasksStore.sync();
- }
- if(!listsStore.getNodeById(selModel.getSelection()[0].get('id'))) { //if the selection no longer exists in the store (it was part of the deleted node(s))
- // change selection to the "All Tasks" list
- selModel.select(0);
- }
-
- // refresh the list view so the task counts will be accurate
- listTree.refreshView();
- }
- }
- });
- },
- /**
- * Handles the list tree's "selectionchange" event.
- * Filters the task store based on the selected list.
- * @param {Ext.selection.RowModel} selModel
- * @param {SimpleTasks.model.List[]} lists
- */
- filterTaskGrid: function(selModel, lists) {
- var list = lists[0],
- tasksStore = this.getTasksStore(),
- listIds = [],
- deleteListBtn = Ext.getCmp('delete-list-btn'),
- deleteFolderBtn = Ext.getCmp('delete-folder-btn'),
- filters = tasksStore.filters.getRange(0, tasksStore.filters.getCount() - 1),
- filterCount = filters.length,
- i = 0;
- // first clear any existing filter
- tasksStore.clearFilter();
- // build an array of all the list_id's in the hierarchy of the selected list
- list.cascadeBy(function(list) {
- listIds.push(list.get('id'));
- });
- // remove any existing "list_id" filter from the filters array
- for(; i < filterCount; i++) {
- if(filters[i].property === 'list_id') {
- filters.splice(i, 1);
- filterCount --;
- }
- }
- // add the new list_ids to the filters array
- filters.push({ property: "list_id", value: new RegExp('^' + listIds.join('$|^') + '$') });
- // apply the filters
- tasksStore.filter(filters);
- // set the center panel's title to the name of the currently selected list
- this.getTaskGrid().setTitle(list.get('name'));
- // enable or disable the "delete list" and "delete folder" buttons depending on what type of node is selected
- if(list.get('id') === -1) {
- deleteListBtn.disable();
- deleteFolderBtn.disable();
- } else if(list.isLeaf()) {
- deleteListBtn.enable();
- deleteFolderBtn.disable();
- } else {
- deleteListBtn.disable();
- deleteFolderBtn.enable();
- }
- // make the currently selected list the default value for the list field on the new task form
- this.getTaskForm().query('[name=list_id]')[0].setValue(list.get('id'));
- },
- /**
- * Handles the list view's "taskdrop" event. Runs when a task is dragged and dropped on a list.
- * Updates the task to belong to the list it was dropped on.
- * @param {SimpleTasks.model.Task} task The Task record that was dropped
- * @param {SimpleTasks.model.List} list The List record that the mouse was over when the drop happened
- */
- updateTaskList: function(task, list) {
- var me = this,
- listId = list.get('id');
- // set the tasks list_id field to the id of the list it was dropped on
- task.set('list_id', listId);
- // save the task to the server
- task.save({
- success: function(task, operation) {
- // refresh the filters on the task list
- me.getTaskGrid().refreshFilters();
- // refresh the lists view so the task counts will be updated.
- me.getListTree().refreshView();
- },
- failure: function(task, operation) {
- var error = operation.getError(),
- msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;
- Ext.MessageBox.show({
- title: 'Move Task Failed',
- msg: msg,
- icon: Ext.Msg.ERROR,
- buttons: Ext.Msg.OK
- });
- }
- });
- },
- /**
- * Handles the list view's "listdrop" event. Runs after a list is reordered by dragging and dropping.
- * Commits the lists new position in the tree to the server.
- * @param {SimpleTasks.model.List} list The List that was dropped
- * @param {SimpleTasks.model.List} overList The List that the List was dropped on
- * @param {String} position `"before"` or `"after"` depending on whether the mouse is above or below the midline of the node.
- */
- reorderList: function(list, overList, position) {
- var listsStore = this.getListsStore();
- if(listsStore.getProxy().type === 'localstorage') {
- listsStore.sync();
- } else {
- Ext.Ajax.request({
- url: 'php/list/move.php',
- jsonData: {
- id: list.get('id'),
- relatedId: overList.get('id'),
- position: position
- },
- success: function(response, options) {
- var responseData = Ext.decode(response.responseText);
- if(!responseData.success) {
- Ext.MessageBox.show({
- title: 'Move Task Failed',
- msg: responseData.message,
- icon: Ext.Msg.ERROR,
- buttons: Ext.Msg.OK
- });
- }
- },
- failure: function(response, options) {
- Ext.MessageBox.show({
- title: 'Move Task Failed',
- msg: response.status + ' ' + response.statusText,
- icon: Ext.Msg.ERROR,
- buttons: Ext.Msg.OK
- });
- }
- });
- }
-
- // refresh the lists view so the task counts will be updated.
- this.getListTree().refreshView();
- },
- /**
- * Handles the initial tasks store "load" event,
- * refreshes the List tree view then removes itself as a handler.
- * @param {SimpleTasks.store.Tasks} tasksStore
- * @param {SimpleTasks.model.Task[]} tasks
- * @param {Boolean} success
- * @param {Ext.data.Operation} operation
- */
- handleTasksLoad: function(tasksStore, tasks, success, operation) {
- var me = this,
- listTree = me.getListTree(),
- selectionModel = listTree.getSelectionModel();
- // refresh the lists view so the task counts will be updated.
- listTree.refreshView();
- // filter the task grid by the selected list
- me.filterTaskGrid(selectionModel, selectionModel.getSelection());
- // remove the event listener after the first run
- tasksStore.un('load', this.handleTasksLoad, this);
- },
- /**
- * Handles the initial lists store "load" event,
- * selects the list tree's root node if the list tree exists, loads the tasks store, then removes itself as a handler.
- * @param {SimpleTasks.store.Lists} listsStore
- * @param {SimpleTasks.model.List[]} lists
- * @param {Boolean} success
- * @param {Ext.data.Operation} operation
- */
- handleListsLoad: function(listsStore, lists, success, operation) {
- var me = this,
- listTree = me.getListTree(),
- tasksStore = me.getTasksStore();
-
- if(listTree) {
- // if the list tree exists when the lists store is first loaded, select the root node.
- // 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.
- // 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)
- listTree.getSelectionModel().select(0);
- }
- // wait until lists are done loading to load tasks since the task grid's "list" column renderer depends on lists store being loaded
- me.getTasksStore().load();
- // if the tasks store is asynchronous (server proxy) attach load handler for refreshing the list counts after loading is complete
- // if local storage is being used, isLoading will be false here since load() will run syncronously, so there is no need
- // to refresh the lists view because load will have happened before the list tree is even rendered
- if(tasksStore.isLoading()) {
- tasksStore.on('load', me.handleTasksLoad, me);
- }
- // remove the event listener after the first run
- listsStore.un('load', me.handleListsLoad, me);
- },
- /**
- * Handles the list tree's "afterrender" event
- * Selects the lists tree's root node, if the list tree exists
- * @param {SimpleTasks.view.lists.Tree} listTree
- */
- handleAfterListTreeRender: function(listTree) {
- listTree.getSelectionModel().select(0);
- },
- /**
- * Handles the lists store's write event.
- * Syncronizes the other read only list stores with the newly saved data
- * @param {SimpleTasks.store.Lists} listsStore
- * @param {Ext.data.Operation} operation
- */
- syncListsStores: function(listsStore, operation) {
- var me = this,
- stores = [
- Ext.getStore('Lists-TaskGrid'),
- Ext.getStore('Lists-TaskEditWindow'),
- Ext.getStore('Lists-TaskForm')
- ],
- listToSync;
-
- Ext.each(operation.getRecords(), function(list) {
- Ext.each(stores, function(store) {
- if(store) {
- listToSync = store.getNodeById(list.getId());
- switch(operation.action) {
- case 'create':
- (store.getNodeById(list.parentNode.getId()) || store.getRootNode()).appendChild(list.copy());
- break;
- case 'update':
- if(listToSync) {
- listToSync.set(list.data);
- listToSync.commit();
- }
- break;
- case 'destroy':
- if(listToSync) {
- listToSync.remove(false);
- }
- }
- }
- });
- });
- },
- /**
- * Handles a mouseenter event on a list tree node.
- * Shows the node's action icons.
- * @param {Ext.tree.View} view
- * @param {SimpleTasks.model.List} list
- * @param {HTMLElement} node
- * @param {Number} rowIndex
- * @param {Ext.EventObject} e
- */
- showActions: function(view, list, node, rowIndex, e) {
- var icons = Ext.DomQuery.select('.x-action-col-icon', node);
- if(view.getRecord(node).get('id') > 0) {
- Ext.each(icons, function(icon){
- Ext.get(icon).removeCls('x-hidden');
- });
- }
- },
- /**
- * Handles a mouseleave event on a list tree node.
- * Hides the node's action icons.
- * @param {Ext.tree.View} view
- * @param {SimpleTasks.model.List} list
- * @param {HTMLElement} node
- * @param {Number} rowIndex
- * @param {Ext.EventObject} e
- */
- hideActions: function(view, list, node, rowIndex, e) {
- var icons = Ext.DomQuery.select('.x-action-col-icon', node);
- Ext.each(icons, function(icon){
- Ext.get(icon).addCls('x-hidden');
- });
- },
- /**
- * Handles the list tree's itemcontextmenu event
- * Shows the list context menu.
- * @param {Ext.grid.View} view
- * @param {SimpleTasks.model.List} list
- * @param {HTMLElement} node
- * @param {Number} rowIndex
- * @param {Ext.EventObject} e
- */
- showContextMenu: function(view, list, node, rowIndex, e) {
- var contextMenu = this.getContextMenu(),
- newListItem = Ext.getCmp('new-list-item'),
- newFolderItem = Ext.getCmp('new-folder-item'),
- deleteFolderItem = Ext.getCmp('delete-folder-item'),
- deleteListItem = Ext.getCmp('delete-list-item');
- if(list.isLeaf()) {
- newListItem.hide();
- newFolderItem.hide();
- deleteFolderItem.hide();
- deleteListItem.show();
- } else {
- newListItem.show();
- newFolderItem.show();
- if(list.isRoot()) {
- deleteFolderItem.hide();
- } else {
- deleteFolderItem.show();
- }
- deleteListItem.hide();
- }
- contextMenu.setList(list);
- contextMenu.showAt(e.getX(), e.getY());
- e.preventDefault();
- }
- });
|