Task Manager App - kendmaclean/pharo12 GitHub Wiki
Okay, let's create a different Spec2 application: a Simple Task Manager.
This app will allow users to:
- Add tasks with descriptions.
- View a list of tasks.
- Mark tasks as complete/incomplete using a checkbox directly in the list.
- Filter tasks (All, Pending, Completed).
- Remove tasks.
Step 1: Define the Data Model (Task)
Object << #Task
slots: { #description . #isComplete };
package: 'MyTaskManager'Class-side convenience creator
Task class >> description: aString
^ self new
description: aString;
yourselfAdd accessor methods, initialization, and a helper method:
"In class Task"
Task >> description
^ description
Task >> description: aString
description := aString
Task >> isComplete
^ isComplete ifNil: [ false ] "Default to false if not set"
Task >> isComplete: aBoolean
isComplete := aBoolean
Task >> toggleComplete
self isComplete: self isComplete not.
Task >> initialize
super initialize.
description := ''.
isComplete := false.
Task >> printOn: aStream
super printOn: aStream.
aStream
nextPut: $(;
nextPutAll: self description;
nextPutAll: (self isComplete ifTrue: [' - Done'] ifFalse: [' - Pending']);
nextPut: $)Step 2: The Repository (TaskRepository)
Instance side:
Object << #TaskRepository
slots: { #tasks };
package: 'MyTaskManager'Class side:
Object class << TaskRepository class
slots: { #uniqueInstance }
TaskRepository class >> default
"Return the singleton instance"
^ self uniqueInstance
TaskRepository class >> reset
"Reset the singleton instance - useful for testing"
uniqueInstance := nil
TaskRepository class >> uniqueInstance
"Lazy initialization of the singleton instance"
^ uniqueInstance ifNil: [ uniqueInstance := self new ]
Instance side:
TaskRepository >> addTask: aTask
tasks add: aTask.
^ aTask
TaskRepository >> initialize
super initialize.
tasks := OrderedCollection new.
self loadSampleData.
TaskRepository >> loadSampleData
"Only load sample data if repository is empty"
tasks ifNotEmpty: [ ^ self ].
self addTask: (Task description: 'Learn Pharo').
self addTask: (Task description: 'Build Spec2 App').
(tasks at: 2) isComplete: true.
self addTask: (Task description: 'Drink Coffee').
TaskRepository >> removeTask: aTask
tasks remove: aTask ifAbsent: [ ].
^ aTask
TaskRepository >> tasks
^ tasksStep 3: Create the Spec Application (TaskManagerApp)
SpPresenter << #TaskManagerApp
slots: {
#displayedTasks .
#selectedTask .
#taskList .
#taskInputField .
#addButton .
#removeButton .
#filterAllRadio .
#filterPendingRadio .
#filterCompletedRadio .
#currentFilter };
package: 'MyTaskManager'Class side:
TaskManagerApp class >> open
<example>
| inst |
inst := self new.
inst open title: 'Simple Task Manager'. "Set a window title"
^ inst
Instance side:
"In class TaskManagerApp"
TaskManagerApp >> addTask
| description newTask |
description := taskInputField text trimBoth.
description ifEmpty: [ ^ self ]. "Do nothing if input is empty"
newTask := Task description: description.
self taskRepository addTask: newTask.
taskInputField text: ''. "Clear input"
taskInputField takeKeyboardFocus.
self applyFilter. "Re-apply filter to show the new task"
TaskManagerApp >> applyFilter
| previouslySelected allTasks |
previouslySelected := selectedTask. "Remember selection"
selectedTask := nil.
removeButton disable.
"Get tasks from repository"
allTasks := self taskRepository tasks.
displayedTasks removeAll.
allTasks do: [ :task |
| shouldInclude all pending completed |
all := (currentFilter == #all).
pending := ( currentFilter == #pending and: task isComplete not ).
completed := ( currentFilter == #completed and: task isComplete ).
shouldInclude := all
or: [ pending
or: completed ].
shouldInclude ifTrue: [
displayedTasks add: task ].
].
taskList items: displayedTasks.
"Try to restore selection if the item is still visible"
(displayedTasks includes: previouslySelected) ifTrue: [
taskList selectItem: previouslySelected.
].
TaskManagerApp >> connectPresenters
"Connect List Selection"
taskList whenSelectionChangedDo: [ :selection |
self onTaskSelection: selection ].
"Connect Filter Changes"
filterAllRadio whenActivatedDo: [
currentFilter := #all.
self applyFilter ].
filterPendingRadio whenActivatedDo: [
currentFilter := #pending.
self applyFilter ].
filterCompletedRadio whenActivatedDo: [
currentFilter := #completed.
self applyFilter ].
"Connect Input Field Submission (Enter key)"
taskInputField whenSubmitDo: [ :text |
self addTask ].
TaskManagerApp >> defaultLayout
| filterLayout inputLayout buttonLayout |
filterLayout := SpBoxLayout newHorizontal
add: filterAllRadio;
add: filterPendingRadio;
add: filterCompletedRadio;
spacing: 10;
yourself.
inputLayout := SpBoxLayout newHorizontal
add: taskInputField expand: true fill: true padding: 0;
add: addButton expand: false fill: false padding: 0;
spacing: 5;
yourself.
buttonLayout := SpBoxLayout newHorizontal
addLast: removeButton expand: false;
yourself.
^ SpBoxLayout newVertical
add: filterLayout height: self class toolbarHeight;
add: taskList expand: true fill: true padding: 0;
add: inputLayout height: self class inputTextHeight;
add: buttonLayout height: self class buttonHeight;
spacing: 5;
yourself
TaskManagerApp >> initialize
super initialize.
"Initialize filtered task collection"
displayedTasks := OrderedCollection new.
currentFilter := #all. "Initial filter state"
self applyFilter.
TaskManagerApp >> initializePresenters
"Task List (Table)"
taskList := self newTable.
taskList
addColumn: ((SpCheckBoxTableColumn
title: 'Done'
evaluated: [ :task | task isComplete ])
onActivation: [ :task | self toggleTaskStatus: task ];
onDeactivation: [ :task | self toggleTaskStatus: task ];
yourself);
addColumn: (SpStringTableColumn
title: 'Description'
evaluated: [ :task | task description ]).
"Input Field"
taskInputField := self newTextInput
placeholder: 'Enter new task description';
yourself.
"Buttons"
addButton := self newButton
label: 'Add Task';
icon: (self iconNamed: #smallAdd);
action: [ self addTask ];
yourself.
removeButton := self newButton
label: 'Remove Task';
icon: (self iconNamed: #smallDelete);
action: [ self removeSelectedTask ];
disable;
yourself.
"Filter Radio Buttons"
filterAllRadio := self newRadioButton label: 'All'.
filterPendingRadio := self newRadioButton label: 'Pending'.
filterCompletedRadio := self newRadioButton label: 'Completed'.
filterAllRadio associatedRadioButtons: { filterPendingRadio. filterCompletedRadio }.
filterAllRadio state: true. "Set the default selection "
TaskManagerApp >> onTaskSelection: aSelectionMode
"enable remove button when a task is selected"
selectedTask := aSelectionMode selectedItem.
selectedTask
ifNil: [ removeButton disable ]
ifNotNil: [ removeButton enable ].
TaskManagerApp >> removeSelectedTask
| confirmMessage |
selectedTask ifNil: [ ^ self ].
confirmMessage := 'Remove task "', selectedTask description, '"?'.
(self confirm: confirmMessage) ifFalse: [ ^ self ].
self taskRepository removeTask: selectedTask.
selectedTask := nil.
removeButton disable.
self applyFilter. "Update the list"
TaskManagerApp >> taskRepository
"Access the task repository singleton"
^ TaskRepository default
TaskManagerApp >> initializeWindow: aWindowPresenter
aWindowPresenter title: 'Simple Task Manager'
TaskManagerApp >> toggleTaskStatus: aTask
aTask ifNil: [ ^ self ].
aTask toggleComplete.
"Re-apply filter as task might need to be hidden/shown"
self applyFilter.
Step 6: How to Run
Run: Open a Playground. Type TaskManagerApp open and "Do it" (Ctrl+D).
You should now have a working Task Manager application!