Tasks.js 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022
  1. /**
  2. * @class SimpleTasks.controller.Tasks
  3. * @extends Ext.app.Controller
  4. */
  5. Ext.define('SimpleTasks.controller.Tasks', {
  6. extend: 'Ext.app.Controller',
  7. models: ['Task'],
  8. stores: ['Tasks'],
  9. views: [
  10. 'tasks.Grid',
  11. 'tasks.Form',
  12. 'tasks.EditWindow',
  13. 'tasks.DefaultTimeWindow',
  14. 'tasks.ReminderWindow',
  15. 'tasks.ContextMenu'
  16. ],
  17. refs: [
  18. {
  19. ref: 'listTree',
  20. selector: 'listTree'
  21. },
  22. {
  23. ref: 'taskForm',
  24. selector: 'taskForm'
  25. },
  26. {
  27. ref: 'taskGrid',
  28. selector: 'taskGrid'
  29. },
  30. {
  31. ref: 'tasksToolbar',
  32. selector: 'tasksToolbar'
  33. },
  34. {
  35. ref: 'taskEditWindow',
  36. selector: 'taskEditWindow',
  37. xtype: 'taskEditWindow',
  38. autoCreate: true
  39. },
  40. {
  41. ref: 'defaultTimeWindow',
  42. selector: 'defaultTimeWindow',
  43. xtype: 'defaultTimeWindow',
  44. autoCreate: true
  45. },
  46. {
  47. ref: 'reminderWindow',
  48. selector: 'reminderWindow',
  49. xtype: 'reminderWindow',
  50. forceCreate: true
  51. },
  52. {
  53. ref: 'contextMenu',
  54. selector: 'tasksContextMenu',
  55. xtype: 'tasksContextMenu',
  56. autoCreate: true
  57. }
  58. ],
  59. init: function() {
  60. var me = this;
  61. me.control(
  62. {
  63. 'taskForm textfield': {
  64. specialkey: me.handleSpecialKey
  65. },
  66. '[iconCls=tasks-new]': {
  67. click: me.focusTaskForm
  68. },
  69. '#delete-task-btn': {
  70. click: me.handleDeleteClick
  71. },
  72. '#delete-task-item': {
  73. click: me.handleDeleteClick
  74. },
  75. '#mark-complete-item': {
  76. click: me.markComplete
  77. },
  78. '#mark-complete-btn': {
  79. click: me.markComplete
  80. },
  81. '#mark-active-item': {
  82. click: me.markActive
  83. },
  84. '#mark-active-btn': {
  85. click: me.markActive
  86. },
  87. '#show-all-btn': {
  88. click: me.filterAll
  89. },
  90. '#show-active-btn': {
  91. click: me.filterActive
  92. },
  93. '#show-complete-btn': {
  94. click: me.filterComplete
  95. },
  96. '#edit-task-item': {
  97. click: me.handleEditItemClick
  98. },
  99. 'taskGrid': {
  100. recordedit: me.updateTask,
  101. deleteclick: me.handleDeleteIconClick,
  102. editclick: me.handleEditIconClick,
  103. reminderselect: me.setReminder,
  104. itemmouseenter: me.showActions,
  105. itemmouseleave: me.hideActions,
  106. selectionchange: me.toggleButtons,
  107. columnresize: me.syncTaskFormFieldWidth,
  108. itemcontextmenu: me.showContextMenu
  109. },
  110. 'tasksToolbar': {
  111. afterrender: me.initShowAll
  112. },
  113. 'taskEditWindow [name=has_reminder]': {
  114. change: me.toggleReminderFields
  115. },
  116. '#cancel-task-edit-btn': {
  117. click: me.hideEditWindow
  118. },
  119. '#save-task-edit-btn': {
  120. click: me.handleSaveTaskClick
  121. },
  122. 'taskEditWindow [name=reminder_date]': {
  123. change: me.syncReminderField
  124. },
  125. 'taskEditWindow [name=reminder_time]': {
  126. change: me.syncReminderField
  127. },
  128. '#toggle-complete-btn': {
  129. click: me.toggleCompleteField
  130. },
  131. '#delete-task-window-btn': {
  132. click: me.deleteTaskAndCloseEditWindow
  133. },
  134. 'defaultTimeWindow [name=default_time]': {
  135. },
  136. '#cancel-default-time-edit-btn': {
  137. click: me.hideDefaultTimeWindow
  138. },
  139. '#save-default-time-btn': {
  140. click: me.saveDefaultTime
  141. },
  142. '[cls=snooze-btn]': {
  143. click: me.snooze
  144. },
  145. '[cls=dismiss-reminder-btn]': {
  146. click: me.dismissReminder
  147. }
  148. }
  149. );
  150. me.initReminderInterval();
  151. },
  152. /**
  153. * Handles a "specialkey" event on an field on the task form.
  154. * Creates a new task if the enter key is pressed.
  155. * @param {Ext.form.field.Text} field
  156. * @param {Ext.EventObject} e
  157. */
  158. handleSpecialKey: function(field, e) {
  159. if(e.getKey() === e.ENTER) {
  160. this.newTask();
  161. }
  162. },
  163. /**
  164. * Creates a new task based on the data currently contained in the task form.
  165. * Saves the new task to the server and adds it to the task list view.
  166. */
  167. newTask: function() {
  168. var me = this,
  169. form = me.getTaskForm(),
  170. basicForm = form.getForm(),
  171. formEl = form.getEl(),
  172. titleField = form.getForm().findField('title'),
  173. task = Ext.create('SimpleTasks.model.Task');
  174. // require title field to have a value
  175. if(!titleField.getValue()) {
  176. return;
  177. }
  178. // update the new task record with the data from the form.
  179. basicForm.updateRecord(task);
  180. // try to blur all of this form's items to make sure that the user can't type into a field while saving
  181. form.items.each(function(item) {
  182. var inputEl = item.getEl().down('input')
  183. if(inputEl) {
  184. inputEl.blur();
  185. }
  186. });
  187. // mask the form element while saving
  188. formEl.mask('saving . . .');
  189. // save the task to the server
  190. task.save({
  191. success: function(task, operation) {
  192. me.getTasksStore().add(task);
  193. me.refreshFiltersAndCount();
  194. me.getTasksStore().sort();
  195. titleField.reset();
  196. titleField.focus();
  197. formEl.unmask();
  198. },
  199. failure: function(task, operation) {
  200. var error = operation.getError(),
  201. msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;
  202. Ext.MessageBox.show({
  203. title: 'Add Task Failed',
  204. msg: msg,
  205. icon: Ext.Msg.ERROR,
  206. buttons: Ext.Msg.OK
  207. });
  208. formEl.unmask();
  209. }
  210. });
  211. },
  212. /**
  213. * Handles the task list's "recordedit" event.
  214. * Updates the task on the server whenever a task is updated using the task grid's cell editor
  215. * @param {SimpleTasks.model.Task} task The task record that was edited
  216. */
  217. updateTask: function(task) {
  218. var me = this;
  219. if(task.modified.done === false) {
  220. task.set('reminder', null);
  221. }
  222. task.save({
  223. success: function(task, operation) {
  224. me.refreshFiltersAndCount();
  225. me.getTasksStore().sort();
  226. },
  227. failure: function(task, operation) {
  228. var error = operation.getError(),
  229. msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;
  230. Ext.MessageBox.show({
  231. title: 'Update Task Failed',
  232. msg: msg,
  233. icon: Ext.Msg.ERROR,
  234. buttons: Ext.Msg.OK
  235. });
  236. }
  237. });
  238. },
  239. /**
  240. * Handles a click on a delete icon in the task grid.
  241. * @param {Ext.grid.View} view
  242. * @param {Number} rowIndex
  243. * @param {Number} colIndex
  244. * @param {Ext.grid.column.Action} column
  245. * @param {EventObject} e
  246. */
  247. handleDeleteIconClick: function(view, rowIndex, colIndex, column, e) {
  248. this.deleteTask(this.getTasksStore().getAt(rowIndex));
  249. },
  250. /**
  251. * Handles a click on the "delete task" button or context menu item
  252. * @param {Ext.button.Button} button
  253. * @param {Ext.EventObject} e
  254. */
  255. handleDeleteClick: function(button, e) {
  256. this.deleteTask(this.getTaskGrid().getSelectionModel().getSelection()[0]);
  257. },
  258. /**
  259. * Deletes the task from the server and updates the view.
  260. * @param {SimpleTasks.model.Task} task
  261. * @param {Function} successCallback A function to call after the task has been deleted successfully
  262. */
  263. deleteTask: function(task, successCallback) {
  264. var me = this;
  265. Ext.Msg.show({
  266. title: 'Delete Task?',
  267. msg: 'Are you sure you want to delete this task?',
  268. buttons: Ext.Msg.YESNO,
  269. fn: function(response) {
  270. if(response === 'yes') {
  271. task.destroy({
  272. success: function(task, operation) {
  273. me.getTasksStore().remove(task);
  274. me.refreshFiltersAndCount();
  275. if(successCallback) {
  276. successCallback();
  277. }
  278. },
  279. failure: function(task, operation) {
  280. var error = operation.getError(),
  281. msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;
  282. Ext.MessageBox.show({
  283. title: 'Delete Task Failed',
  284. msg: msg,
  285. icon: Ext.Msg.ERROR,
  286. buttons: Ext.Msg.OK
  287. });
  288. }
  289. });
  290. }
  291. }
  292. });
  293. },
  294. /**
  295. * Refreshes the task grid's list filter, and the task counts in the list tree
  296. */
  297. refreshFiltersAndCount: function() {
  298. // refresh the task filters
  299. this.getTaskGrid().refreshFilters();
  300. // refresh the lists list view so that the task counts will be correct
  301. this.getListTree().refreshView();
  302. },
  303. /**
  304. * Handles a click on the "Edit" context menu item
  305. * @param {Ext.menu.Item} item
  306. * @param {EventObject} e
  307. */
  308. handleEditItemClick: function(item, e) {
  309. this.showEditWindow(this.getContextMenu().getTask());
  310. },
  311. /**
  312. * Handles a click on the "Edit Task" action column
  313. * @param {Ext.grid.View} view
  314. * @param {Number} rowIndex
  315. * @param {Number} colIndex
  316. * @param {Ext.grid.column.Action} column
  317. * @param {EventObject} e
  318. */
  319. handleEditIconClick: function(view, rowIndex, colIndex, column, e) {
  320. this.showEditWindow(view.getRecord(view.findTargetByEvent(e)));
  321. },
  322. /**
  323. * Handles the task grid's "selectionchange" event.
  324. * Disables or enables the task-related toolbar buttons depending on whether or not there is a selection.
  325. * @param {Ext.selection.RowModel} selModel
  326. * @param {SimpleTasks.model.Task[]} tasks
  327. */
  328. toggleButtons: function(selModel, tasks) {
  329. var deleteTaskBtn = Ext.getCmp('delete-task-btn'),
  330. markCompleteBtn = Ext.getCmp('mark-complete-btn'),
  331. markActiveBtn = Ext.getCmp('mark-active-btn');
  332. if(tasks.length === 0) {
  333. deleteTaskBtn.disable();
  334. markCompleteBtn.disable();
  335. markActiveBtn.disable();
  336. } else {
  337. deleteTaskBtn.enable();
  338. markCompleteBtn.enable();
  339. markActiveBtn.enable();
  340. }
  341. },
  342. /**
  343. * Handles a click on the "New Task" button or context menu item
  344. * focuses the title field on the new task form
  345. * @param {Ext.Component} component
  346. * @param {Ext.EventObject} e
  347. */
  348. focusTaskForm: function(component, e) {
  349. this.getTaskForm().query('[name=title]')[0].focus();
  350. },
  351. /**
  352. * Handles a click on the "Mark Complete" button or menu item
  353. * Sets the selected task's "done" field to true
  354. * @param {Ext.Component} component
  355. * @param {Ext.EventObject} e
  356. */
  357. markComplete: function(component, e) {
  358. var contextMenu = this.getContextMenu(),
  359. task = contextMenu.isVisible() ? contextMenu.getTask() : this.getTaskGrid().getSelectionModel().getSelection()[0];
  360. task.set('done', true);
  361. task.set('reminder', null);
  362. task.save({
  363. failure: function(task, operation) {
  364. var error = operation.getError(),
  365. msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;
  366. Ext.MessageBox.show({
  367. title: 'Mark Complete Failed',
  368. msg: msg,
  369. icon: Ext.Msg.ERROR,
  370. buttons: Ext.Msg.OK
  371. });
  372. }
  373. });
  374. this.refreshFiltersAndCount();
  375. },
  376. /**
  377. * Handles a click on the "Mark Active" button
  378. * Sets the selected task's "done" field to false
  379. * @param {Ext.button.Button} button
  380. * @param {Ext.EventObject} e
  381. */
  382. markActive: function(button, e) {
  383. var contextMenu = this.getContextMenu(),
  384. task = contextMenu.isVisible() ? contextMenu.getTask() : this.getTaskGrid().getSelectionModel().getSelection()[0];
  385. task.set('done', false);
  386. task.save({
  387. failure: function(task, operation) {
  388. var error = operation.getError(),
  389. msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;
  390. Ext.MessageBox.show({
  391. title: 'Mark Active Failed',
  392. msg: msg,
  393. icon: Ext.Msg.ERROR,
  394. buttons: Ext.Msg.OK
  395. });
  396. }
  397. });
  398. this.refreshFiltersAndCount();
  399. },
  400. /**
  401. * Handles the task grid columnresize event.
  402. * Synchronizes the width the column's associated form field with the width of the column
  403. * @param {Ext.grid.header.Container} headerContainer
  404. * @param {Ext.column.Column} column
  405. * @param {Number} width The new column width
  406. */
  407. syncTaskFormFieldWidth: function(headerContainer, column, width) {
  408. var field = this.getTaskForm().query('[name=' + column.dataIndex + ']')[0];
  409. if (field) {
  410. field.setWidth(width - 5);
  411. }
  412. },
  413. /**
  414. * Handles a click on the "Show All" button. Removes any filter on the done field so that all tasks will be displayed
  415. * @param {Ext.button.Button} button
  416. * @param {Ext.EventObject} e
  417. */
  418. filterAll: function(button, e) {
  419. var tasksStore = this.getTasksStore(),
  420. filters = tasksStore.filters.getRange(0, tasksStore.filters.getCount() - 1),
  421. filterCount = filters.length,
  422. i = 0;
  423. if(button.pressed) {
  424. tasksStore.clearFilter();
  425. for(; i < filterCount; i++) {
  426. if(filters[i].property === 'done') {
  427. filters.splice(i, 1);
  428. filterCount --;
  429. }
  430. }
  431. tasksStore.filter(filters);
  432. } else {
  433. button.toggle();
  434. }
  435. },
  436. /**
  437. * Handles a click on the "Show Active" button. Filters tasks by done = false
  438. * @param {Ext.button.Button} button
  439. * @param {Ext.EventObject} e
  440. */
  441. filterActive: function(button, e) {
  442. var tasksStore = this.getTasksStore(),
  443. filters = tasksStore.filters.getRange(0, tasksStore.filters.getCount() - 1),
  444. filterCount = filters.length,
  445. i = 0;
  446. if(button.pressed) {
  447. tasksStore.clearFilter();
  448. for(; i < filterCount; i++) {
  449. if(filters[i].property === 'done') {
  450. filters.splice(i, 1);
  451. filterCount --;
  452. }
  453. }
  454. filters.push({ property: 'done', value: false });
  455. this.getTasksStore().filter(filters);
  456. } else {
  457. button.toggle();
  458. }
  459. },
  460. /**
  461. * Handles a click on the "Show Complete" button. Filters tasks by done = true.
  462. * @param {Ext.button.Button} button
  463. * @param {Ext.EventObject} e
  464. */
  465. filterComplete: function(button, e) {
  466. var tasksStore = this.getTasksStore(),
  467. filters = tasksStore.filters.getRange(0, tasksStore.filters.getCount() - 1),
  468. filterCount = filters.length,
  469. i = 0;
  470. if(button.pressed) {
  471. tasksStore.clearFilter();
  472. for(; i < filterCount; i++) {
  473. if(filters[i].property === 'done') {
  474. filters.splice(i, 1);
  475. filterCount --;
  476. }
  477. }
  478. filters.push({ property: 'done', value: true });
  479. this.getTasksStore().filter(filters);
  480. } else {
  481. button.toggle();
  482. }
  483. },
  484. /**
  485. * Handles the tasks toolbar's render event
  486. * Initializes the "Show All" Button to the pressed state
  487. * @param {SimpleTasks.view.Toolbar} toolbar
  488. */
  489. initShowAll: function(toolbar) {
  490. toolbar.getComponent('show-all-btn').toggle();
  491. },
  492. /**
  493. * Handles a mouseenter event on a task grid item.
  494. * Shows the item's action icons.
  495. * @param {Ext.grid.View} view
  496. * @param {SimpleTasks.model.Task} task
  497. * @param {HTMLElement} node
  498. * @param {Number} rowIndex
  499. * @param {Ext.EventObject} e
  500. */
  501. showActions: function(view, task, node, rowIndex, e) {
  502. var icons = Ext.DomQuery.select('.x-action-col-icon', node);
  503. Ext.each(icons, function(icon){
  504. Ext.get(icon).removeCls('x-hidden');
  505. });
  506. },
  507. /**
  508. * Handles a mouseleave event on a task grid item.
  509. * Hides the item's action icons.
  510. * @param {Ext.grid.View} view
  511. * @param {SimpleTasks.model.Task} task
  512. * @param {HTMLElement} node
  513. * @param {Number} rowIndex
  514. * @param {Ext.EventObject} e
  515. */
  516. hideActions: function(view, task, node, rowIndex, e) {
  517. var icons = Ext.DomQuery.select('.x-action-col-icon', node);
  518. Ext.each(icons, function(icon){
  519. Ext.get(icon).addCls('x-hidden');
  520. });
  521. },
  522. /**
  523. * Handles the task grid's itemcontextmenu event
  524. * Shows the task context menu.
  525. * @param {Ext.grid.View} view
  526. * @param {SimpleTasks.model.Task} task
  527. * @param {HTMLElement} node
  528. * @param {Number} rowIndex
  529. * @param {Ext.EventObject} e
  530. */
  531. showContextMenu: function(view, task, node, rowIndex, e) {
  532. var contextMenu = this.getContextMenu(),
  533. markCompleteItem = Ext.getCmp('mark-complete-item'),
  534. markActiveItem = Ext.getCmp('mark-active-item');
  535. if(task.get('done')) {
  536. markCompleteItem.hide();
  537. markActiveItem.show();
  538. } else {
  539. markCompleteItem.show();
  540. markActiveItem.hide();
  541. }
  542. contextMenu.setTask(task);
  543. contextMenu.showAt(e.getX(), e.getY());
  544. e.preventDefault();
  545. },
  546. /**
  547. * Shows the "Edit Task" window
  548. * @param {SimpleTasks.model.Task} task the task to edit
  549. */
  550. showEditWindow: function(task) {
  551. var me = this,
  552. taskEditWindow = me.getTaskEditWindow(),
  553. form = taskEditWindow.down('form').getForm(),
  554. reminderCheckbox = form.findField('has_reminder'),
  555. dateField = form.findField('reminder_date'),
  556. timeField = form.findField('reminder_time'),
  557. reminder = task.get('reminder');
  558. // Set the tasks title as the title of the edit window
  559. taskEditWindow.setTitle('Edit Task - ' + task.get('title'));
  560. // load the task data into the form
  561. taskEditWindow.down('form').loadRecord(task);
  562. // set the text of the toggle-complete button depending on the tasks "done" value
  563. Ext.getCmp('toggle-complete-btn').setText(task.get('done') ? 'Mark Active' : 'Mark Complete');
  564. taskEditWindow.show();
  565. if(task.get('reminder')) {
  566. // if the task already has a reminder set check the reminder checkbox and populate the reminder date and reminder time fields
  567. reminderCheckbox.setValue(true);
  568. dateField.setValue(Ext.Date.clearTime(reminder, true));
  569. timeField.setValue(Ext.Date.format(reminder, timeField.format));
  570. } else {
  571. // if the task does not have a reminder set uncheck the reminder checkbox and set the reminder date and time fields to null
  572. reminderCheckbox.setValue(false);
  573. dateField.setValue(null);
  574. timeField.setValue(null);
  575. }
  576. if(task.get('done')) {
  577. // if the task is done disable the reminder checkbox (reminders cannot be set on completed tasks)
  578. reminderCheckbox.disable();
  579. } else {
  580. reminderCheckbox.enable();
  581. }
  582. },
  583. /**
  584. * Handles a click on the "Edit Task" window's cancel button
  585. * Hides the "Edit Task" window
  586. * @param {Ext.Button} button
  587. * @param {Ext.EventObject} e
  588. */
  589. hideEditWindow: function(button, e) {
  590. this.getTaskEditWindow().close();
  591. },
  592. /**
  593. * Handles the change event on the task edit window's "has_reminder" checkbox
  594. * Toggles the visibility of the reminder date and time fields
  595. * @param {Ext.form.field.Checkbox} checkbox
  596. * @param {Boolean} newValue
  597. * @param {Boolean} oldValue
  598. */
  599. toggleReminderFields: function(checkbox, newValue, oldValue) {
  600. var taskEditWindow = this.getTaskEditWindow(),
  601. windowEl = taskEditWindow.getEl(),
  602. form = taskEditWindow.down('form').getForm(),
  603. task = form.getRecord(),
  604. dateField = form.findField('reminder_date'),
  605. timeField = form.findField('reminder_time'),
  606. defaultTimeDate, defaultTimeMilliseconds;
  607. if(newValue) { // if the "has reminder" checkbox was checked
  608. windowEl.mask('loading');
  609. // get the default reminder time from the server or cache
  610. this.getDefaultReminderTime(function(defaultTime) {
  611. // enable the date and time fields
  612. dateField.enable();
  613. timeField.enable();
  614. if(!dateField.getValue()) {
  615. // if the reminder date has not already been set, default the reminder date to the task's due date
  616. // or the current date if the task does not have a due date
  617. dateField.setValue(task.get('due') || Ext.Date.clearTime(new Date()));
  618. timeField.setValue(defaultTime);
  619. }
  620. // set the form's hidden reminder field by combining the reminder date and time fields
  621. defaultTimeDate = timeField.getValue();
  622. defaultTimeMilliseconds = defaultTimeDate - Ext.Date.clearTime(defaultTimeDate, true);
  623. form.findField('reminder').setValue(new Date(dateField.getValue().getTime() + defaultTimeMilliseconds));
  624. windowEl.unmask();
  625. });
  626. } else { // if the "has reminder" checkbox was unchecked
  627. // nullify the form's hidden reminder field and disable the reminder date and time fields
  628. form.findField('reminder').setValue(null);
  629. dateField.disable();
  630. timeField.disable();
  631. }
  632. },
  633. /**
  634. * Handles a click on the "Task Edit" window's save button.
  635. * @param {Ext.button.Button} button
  636. * @param {Ext.EventObject} e
  637. */
  638. handleSaveTaskClick: function(button, e) {
  639. this.saveEditWindow();
  640. },
  641. /**
  642. * Updates the task record with the form data from the edit window and saves the task to the server.
  643. */
  644. saveEditWindow: function() {
  645. var taskEditWindow = this.getTaskEditWindow(),
  646. windowEl = taskEditWindow.getEl(),
  647. form = taskEditWindow.down('form').getForm(),
  648. task = form.getRecord();
  649. if(form.isValid()) {
  650. windowEl.mask('saving');
  651. form.updateRecord(task);
  652. if(task.modified.done === false) {
  653. task.set('reminder', null);
  654. }
  655. task.save({
  656. success: function(task, operation) {
  657. windowEl.unmask();
  658. taskEditWindow.close();
  659. },
  660. failure: function(task, operation) {
  661. var error = operation.getError(),
  662. msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;
  663. Ext.MessageBox.show({
  664. title: 'Edit Task Failed',
  665. msg: msg,
  666. icon: Ext.Msg.ERROR,
  667. buttons: Ext.Msg.OK
  668. });
  669. windowEl.unmask();
  670. }
  671. })
  672. } else {
  673. Ext.Msg.alert('Invalid Data', 'Please correct form errors');
  674. }
  675. },
  676. /**
  677. * Syncronizes the value of the edit window's hidden reminder field whenever "reminder_date", or "reminder_time" is changed
  678. * @param {Ext.form.field.Picker} field the date or time picker
  679. * @param {Date} oldValue
  680. * @param {Date} newValue
  681. */
  682. syncReminderField: function(field, oldValue, newValue) {
  683. var form = this.getTaskEditWindow().down('form').getForm(),
  684. reminderField = form.findField('reminder'),
  685. date = form.findField('reminder_date').getValue(),
  686. timeDate = form.findField('reminder_time').getValue(),
  687. time, reminderDate;
  688. if(date && timeDate) {
  689. time = timeDate - Ext.Date.clearTime(timeDate, true),
  690. reminderDate = new Date(date.getTime() + time);
  691. reminderField.setValue(reminderDate);
  692. }
  693. },
  694. /**
  695. * Toggles the edit window's "done" field to true when the "Mark Complete" or "Mark Active" button on the edit window is clicked
  696. * @param {Ext.button.Button} button
  697. * @param {Ext.EventObject} e
  698. */
  699. toggleCompleteField: function(button, e) {
  700. var taskEditWindow = this.getTaskEditWindow(),
  701. doneField = taskEditWindow.down('form').getForm().findField('done');
  702. if(doneField.getValue() === 'true') {
  703. doneField.setValue(false);
  704. } else {
  705. doneField.setValue(true);
  706. }
  707. this.saveEditWindow();
  708. },
  709. /**
  710. * Handles a click on the "Delete" button on the edit window.
  711. * Deletes the task and closes the edit window
  712. * @param {Ext.button.Button} button
  713. * @param {Ext.EventObject} e
  714. */
  715. deleteTaskAndCloseEditWindow: function(button, e) {
  716. var me = this,
  717. taskEditWindow = me.getTaskEditWindow(),
  718. task = taskEditWindow.down('form').getRecord();
  719. me.deleteTask(task, function() {
  720. me.getTaskEditWindow().close();
  721. });
  722. },
  723. /**
  724. * Handles the Task Grid's `reminderselect` event
  725. * Sets a task's reminder
  726. * @param {SimpleTasks.model.Task} task the underlying record of the row that was clicked to show the reminder menu
  727. * @param {String|Number} value The value that was selected
  728. */
  729. setReminder: function(task, value) {
  730. var me = this,
  731. defaultTimeWindow = me.getDefaultTimeWindow(),
  732. defaultTimeField = defaultTimeWindow.down('form').getForm().findField('default_time'),
  733. defaultTimeDate, defaultTimeMilliseconds;
  734. me.getDefaultReminderTime(function(defaultTime) {
  735. if(value === 'set') {
  736. // if the user selected "Set Default Time", show the default time window.
  737. defaultTimeField.setValue(defaultTime);
  738. defaultTimeWindow.show();
  739. } else {
  740. if(Ext.isNumber(value)) {
  741. // if the user selected a reminder time, set the reminder by adding the user selected value to the due date
  742. defaultTimeDate = Ext.Date.parse(defaultTime, defaultTimeField.format);
  743. defaultTimeMilliseconds = defaultTimeDate - Ext.Date.clearTime(defaultTimeDate, true);
  744. task.set('reminder', new Date(task.get('due').getTime() - (value * 86400000) + defaultTimeMilliseconds));
  745. } else {
  746. // if the user selected "No Reminder" set the reminder field to null
  747. task.set('reminder', null);
  748. }
  749. task.save({
  750. failure: function(task, operation) {
  751. var error = operation.getError(),
  752. msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;
  753. Ext.MessageBox.show({
  754. title: 'Set Reminder Failed',
  755. msg: msg,
  756. icon: Ext.Msg.ERROR,
  757. buttons: Ext.Msg.OK
  758. });
  759. }
  760. });
  761. }
  762. });
  763. },
  764. /**
  765. * Gets the default reminder time and passes it to the callback function.
  766. * Retrieves default reminder time from the server on the first call, then caches it for future calls.
  767. * @param {Function} callback
  768. */
  769. getDefaultReminderTime: function(callback) {
  770. var me = this,
  771. defaultReminderTime;
  772. if(me.defaultReminderTime) {
  773. callback(me.defaultReminderTime);
  774. } else {
  775. me.defaultReminderTime = '8:00 AM'; // the default time if no value can be retrieved from storage
  776. if (SimpleTasksSettings.useLocalStorage) {
  777. defaultReminderTime = localStorage.getItem('SimpleTasks-defaultReminderTime');
  778. if (defaultReminderTime) {
  779. me.defaultReminderTime = defaultReminderTime;
  780. }
  781. callback(me.defaultReminderTime);
  782. } else {
  783. Ext.Ajax.request({
  784. url: 'php/config/read.php',
  785. params: {
  786. key: 'default.reminder.time'
  787. },
  788. success: function(response, options) {
  789. var responseData = Ext.decode(response.responseText);
  790. if(responseData.success && responseData.value) {
  791. me.defaultReminderTime = responseData.value;
  792. }
  793. callback(me.defaultReminderTime);
  794. },
  795. failure: function(response, options) {
  796. callback(me.defaultReminderTime);
  797. }
  798. });
  799. }
  800. }
  801. },
  802. /**
  803. * Hides the default reminder time window when the cancel button is clicked
  804. * @param {Ext.button.Button} button
  805. * @param {Ext.EventObject} e
  806. */
  807. hideDefaultTimeWindow: function(button, e) {
  808. this.getDefaultTimeWindow().close();
  809. },
  810. /**
  811. * Saves the default reminder time to the server when the OK button is clicked
  812. * @param {Ext.button.Button} button
  813. * @param {Ext.EventObject} e
  814. */
  815. saveDefaultTime: function(button, e) {
  816. var me = this,
  817. defaultTimeWindow = me.getDefaultTimeWindow(),
  818. windowEl = defaultTimeWindow.getEl(),
  819. time = defaultTimeWindow.down('form').getForm().findField('default_time').getRawValue();
  820. if (SimpleTasksSettings.useLocalStorage) {
  821. localStorage.setItem('SimpleTasks-defaultReminderTime', time);
  822. me.defaultReminderTime = time;
  823. defaultTimeWindow.close();
  824. } else {
  825. windowEl.mask('saving');
  826. Ext.Ajax.request({
  827. url: 'php/config/update.php',
  828. params: {
  829. key: 'default.reminder.time',
  830. value: time
  831. },
  832. success: function(response, options) {
  833. var responseData = Ext.decode(response.responseText);
  834. if(responseData.success) {
  835. me.defaultReminderTime = time;
  836. defaultTimeWindow.close();
  837. } else {
  838. Ext.MessageBox.show({
  839. title: 'Set Default Time Failed',
  840. msg: responseData.message,
  841. icon: Ext.Msg.ERROR,
  842. buttons: Ext.Msg.OK
  843. });
  844. }
  845. windowEl.unmask();
  846. },
  847. failure: function(response, options) {
  848. Ext.MessageBox.show({
  849. title: 'Set Default Time Failed',
  850. msg: response.status + ' ' + response.statusText,
  851. icon: Ext.Msg.ERROR,
  852. buttons: Ext.Msg.OK
  853. });
  854. windowEl.unmask();
  855. }
  856. });
  857. }
  858. },
  859. /**
  860. * Initializes checking for tasks that have passed their reminder date at 10 second intervals.
  861. */
  862. initReminderInterval: function() {
  863. var me = this,
  864. now, reminderDate;
  865. setInterval(function() {
  866. now = new Date();
  867. me.getTasksStore().each(function(task) {
  868. reminderDate = task.get('reminder');
  869. if(reminderDate && reminderDate < now && !task.get('done')) {
  870. me.showReminderWindow(task);
  871. }
  872. });
  873. }, 10000);
  874. },
  875. /**
  876. * Shows the reminder window for a given task
  877. * @param {SimpleTasks.model.Task} task
  878. */
  879. showReminderWindow: function(task) {
  880. var reminderWindow = this.getReminderWindow(),
  881. reminderDetailsBox = reminderWindow.down('[cls=tasks-reminder-details]'),
  882. title = task.get('title');
  883. task.set('reminder', null);
  884. task.save({
  885. failure: function(task, operation) {
  886. var error = operation.getError(),
  887. msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;
  888. Ext.MessageBox.show({
  889. title: 'Clear Reminder Failed',
  890. msg: msg,
  891. icon: Ext.Msg.ERROR,
  892. buttons: Ext.Msg.OK
  893. });
  894. }
  895. });
  896. reminderWindow.setTask(task);
  897. reminderWindow.setTitle('Reminder - ' + title);
  898. reminderDetailsBox.update({
  899. title: title,
  900. due: task.get('due')
  901. });
  902. reminderWindow.show();
  903. },
  904. /**
  905. * Handles a click on the snooze button on the reminder window.
  906. * Sets the task's reminder date to the current date plus snooze time selected
  907. * @param {Ext.button.Button} button
  908. * @param {Ext.EventObject} e
  909. */
  910. snooze: function(button, e) {
  911. var reminderWindow = button.findParentByType('window'),
  912. task = reminderWindow.getTask(),
  913. snoozeMilliseconds = reminderWindow.down('[name=snooze_time]').getValue() * 60000,
  914. reminderDate = new Date(new Date().getTime() + snoozeMilliseconds);
  915. task.set('reminder', reminderDate);
  916. task.save({
  917. failure: function(task, operation) {
  918. var error = operation.getError(),
  919. msg = Ext.isObject(error) ? error.status + ' ' + error.statusText : error;
  920. Ext.MessageBox.show({
  921. title: 'Set Reminder Failed',
  922. msg: msg,
  923. icon: Ext.Msg.ERROR,
  924. buttons: Ext.Msg.OK
  925. });
  926. }
  927. });
  928. reminderWindow.close();
  929. },
  930. /**
  931. * Handle's a click on the reminder window's dismiss button.
  932. * Hides the reminder window.
  933. * @param {Ext.button.Button} button
  934. * @param {Ext.EventObject} e
  935. */
  936. dismissReminder: function(button, e) {
  937. button.findParentByType('window').close();
  938. }
  939. });