Thursday, June 25, 2009

Creating a calendar using Newspeak and Hopscotch

For this post I'm going to show the code for a little calendar UI fragment created using the Newspeak programming language and the Hopscotch framework.

Calendar


Here's how the calendar looks:

Hopscotch calendar experiment

(As you can see, I'm focusing on the functionality for the moment).

The code



As described in "Hopscotch: Towards User Interface Composition" this framework promotes the separation between data (the subject) and the UI elements (presenter) . For this calendar fragment the data part will be the given date and the UI part will be a series of UI elements that represent the days of a month.

Here's an overview of the definition for the HCalendar class:


class HCalendar usingLib: platform = NewspeakObject (
|
...
|
)
(

class CalendarSubject for: date = Subject (
|
...
|
)
(
...
)

class CalendarPresenter = Presenter (
|
...
|
)
(
...
))




The subject



As mentioned above the subject only holds a given date. Some operations are added to make it easy to manipulate it.


class CalendarSubject for: date = Subject (
|
private date = date.
|
)
('as yet unclassified'
changeDayTo: newDay <Number> = (
date:: Date year: year
month: (month name)
day: newDay.
)

createPresenter = (
^CalendarPresenter new subject: self.
)

day ^ <Number> = (
^date dayOfMonth.
)

month ^ <Month>= (
^date month.
)

moveToNextMonth = (
date:: date addMonths: 1.
)

moveToPreviousMonth = (
date:: date addMonths: -1.
)

year ^ <Number> = (
^date year.
))

The presenter


The presenter class is more interesting. It takes the date from the subject and tries to create a representation of the month using Hopscotch fragments. The following snippet shows an overview of the presenter class.


class CalendarPresenter = Presenter (
"Calendar presenter, shows the days of the a month."
|

protected weeksRow
protected monthHolder
|
)
('as yet unclassified'


definition = (
monthHolder::
holder: [
row: {
link: '<' action: [ subject moveToPreviousMonth.
refreshHolders. ].
blank: 1.
column: { header .
weeks. }.
blank: 1.
link: '>' action: [ subject moveToNextMonth.
refreshHolders. ].

}.].
^monthHolder.
)

fragmentForDaysNotInCurrentMonth = (
^label: ' '.
)

header = (
|headerRow|
headerRow:: row: {
filler.
label:: subject month name, ' ' , subject year asString.
filler.
}.
^headerRow.
)


refreshHolders= (
monthHolder refresh.
)

weekDayFragmentFor: dayNumber = (
|dayNumberText|
dayNumberText:: dayNumber asString.

^link: dayNumberText
action: [ subject changeDayTo: dayNumber.
highlightSelectedDay.
].
)

highlightSelectedDay = (
...
)


weeks = (
...
)

addFirstWeekTo: result withDaysFromPreviousMonth: previousMonthDays = (
...
)

addLastWeekTo: result weeksToShow: weeksToShow lastDayAdded: lastDayAdded daysInNextMonthToShow: nextMonthDays= (
...
)

addMonthWeeksTo: result weeksToShow: weeksToShow lastDayAdded: lastday= (
...
)

columnSeparator = (
...
)

createColumnsFromWeekArray: weekArray = (
...
)

)



The weeks method is where most of the work of creating the calendar is done. For space reasons I'm not including it here, see the link at the end of the post for the complete code.

Reusing the calendar



Now that we have the definition of the calendar presenter and subject we can reuse it to create more interesting fragments. For example the following definition could be used to create a date range picker.

class HDateRange usingLib: platform = (
"Date range selector."
|
...
|
)
(

class DateRangePresenter = Presenter (
"Presenter for date range."
|
dateRangeTextHolder
|
)
(
calendar: dateSubject = (
|aCalendar|
aCalendar:: dateSubject createPresenter.
aCalendar onChange: [ dateRangeTextHolder refresh ].
^aCalendar.
)

definition = (
dateRangeTextHolder::
holder: [label: subject initialDate selectedDate asString, ' - ',
subject finalDate selectedDate asString].
^heading: dateRangeTextHolder
details: [
row: {
calendar: subject initialDate .
blank:49.
calendar: subject finalDate.
}]
)

)

class DateRangeSubject from: initial to: final= Subject (
"Data for the date range."
|
private initialDate = (HCalendar usingLib: platform) CalendarSubject for: initial.
private finalDate = (HCalendar usingLib: platform) CalendarSubject for: final.
|
)
(
createPresenter = (
^ (DateRangePresenter new) subject: self.
)))




Code for this post can be found here.