nemo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/INSTALL +5 -0
- data/README +25 -0
- data/lib/nemo.rb +47 -0
- data/lib/nemo/components.rb +4 -0
- data/lib/nemo/components/calendar.rb +191 -0
- data/lib/nemo/components/confirm.rb +30 -0
- data/lib/nemo/components/multiattr.rb +93 -0
- data/lib/nemo/examples.rb +5 -0
- data/lib/nemo/examples/date_picker.rb +74 -0
- data/lib/nemo/examples/person_editor.rb +293 -0
- data/lib/nemo/metaobject.rb +5 -0
- data/lib/nemo/metaobject/acollector.rb +79 -0
- data/lib/nemo/metaobject/attributes.rb +282 -0
- data/lib/nemo/metaobject/metaobject.rb +86 -0
- data/lib/nemo/metaobject/validation.rb +32 -0
- data/lib/nemo/util.rb +31 -0
- data/lib/nemo/util/accessors.rb +85 -0
- data/lib/nemo/util/blankslate.rb +6 -0
- data/lib/nemo/util/collector.rb +54 -0
- data/lib/nemo/util/date.rb +41 -0
- data/lib/nemo/visitors.rb +4 -0
- data/lib/nemo/visitors/editor.rb +186 -0
- data/lib/nemo/visitors/viewer.rb +63 -0
- data/lib/nemo/visitors/visitor.rb +14 -0
- data/nemo.gemspec +23 -0
- data/nemo_rdoc.bat +1 -0
- data/test/date_picker.rb +14 -0
- data/test/nemo/calendar.css +26 -0
- data/test/nemo/calendar.gif +0 -0
- data/test/nemo/person_editor.css +40 -0
- data/test/person_editor.rb +14 -0
- data/test/unit_test.rb +128 -0
- metadata +86 -0
data/INSTALL
ADDED
data/README
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
== Nemo
|
2
|
+
|
3
|
+
== Copyright and License
|
4
|
+
|
5
|
+
Copyright (c) 2004, 2005 by Kevin Howe (kh@newclear.ca).
|
6
|
+
|
7
|
+
Released under the same terms of license as Ruby.
|
8
|
+
|
9
|
+
== Introduction
|
10
|
+
|
11
|
+
Nemo is a Ruby port of Mewa
|
12
|
+
|
13
|
+
http://www.adrian-lienhard.ch/files/mewa.pdf
|
14
|
+
|
15
|
+
using Michael Neumann's Wee
|
16
|
+
|
17
|
+
http://rubyforge.org/projects/wee
|
18
|
+
|
19
|
+
as the Seaside2 equivalent.
|
20
|
+
|
21
|
+
Nemo is a web-application platform that uses object metadata to automatically construct web-interfaces (Editors and Viewers). It is highly object-oriented with strong emphasis on reusable components.
|
22
|
+
|
23
|
+
== Status
|
24
|
+
|
25
|
+
Nemo is build on top of Wee, and should not be considered production ready until Wee itself is ready.
|
data/lib/nemo.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Ruby port of Mewa
|
2
|
+
#
|
3
|
+
# Meta-level Architecture for Generic Web-Application Construction
|
4
|
+
#
|
5
|
+
# http://www.adrian-lienhard.ch/files/mewa.pdf
|
6
|
+
#
|
7
|
+
# Author:: Kevin Howe
|
8
|
+
# License:: Distributes under the same terms as Ruby
|
9
|
+
#
|
10
|
+
module Nemo
|
11
|
+
|
12
|
+
Version = "0.1.0"
|
13
|
+
|
14
|
+
require 'date'
|
15
|
+
require 'wee'
|
16
|
+
require 'wee/utils/cache'
|
17
|
+
require 'nemo/util'
|
18
|
+
require 'nemo/components'
|
19
|
+
require 'nemo/metaobject'
|
20
|
+
require 'nemo/visitors'
|
21
|
+
require 'nemo/examples'
|
22
|
+
|
23
|
+
module_function
|
24
|
+
|
25
|
+
# Define a new MetaObject
|
26
|
+
#
|
27
|
+
def metaobject_for(*args, &block)
|
28
|
+
Nemo::MetaObject::MetaObject.new(*args, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
class Wee::HtmlCanvas
|
34
|
+
|
35
|
+
# Nemo's standard calendar button
|
36
|
+
#
|
37
|
+
# class MyComponent < Wee::Component
|
38
|
+
# def render
|
39
|
+
# r.nemo_calendar_button.callback {}
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
def nemo_calendar_button
|
44
|
+
image_button.src('/nemo/calendar.gif').width(16).height(16).alt('Calendar').style('border: 0')
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# Browsable Calendar component
|
2
|
+
#
|
3
|
+
# =Use as a date picker
|
4
|
+
# By default, clicking a day will <tt>answer</tt> with an associated Date object.
|
5
|
+
#
|
6
|
+
# =Use as a calendar viewer
|
7
|
+
# No <tt>answer</tt> is given when in browse mode:
|
8
|
+
#
|
9
|
+
# call( Nemo::Components::MiniCalendar.new(date).browse )
|
10
|
+
#
|
11
|
+
# =CSS Styles
|
12
|
+
# Overload render_styles to add a custom <link> or <style> tag.
|
13
|
+
#
|
14
|
+
class Nemo::Components::MiniCalendar < Wee::Component
|
15
|
+
|
16
|
+
# Browse mode: if true, no answer will be given
|
17
|
+
attr_accessor :browse
|
18
|
+
|
19
|
+
# Holds the current chosen date
|
20
|
+
attr_accessor :date
|
21
|
+
|
22
|
+
# Initialize the MiniCalendar
|
23
|
+
#
|
24
|
+
def initialize(date=Date.today)
|
25
|
+
super()
|
26
|
+
@month = Date.new(date.year, date.month, 1)
|
27
|
+
@day = date
|
28
|
+
@browse = false
|
29
|
+
end
|
30
|
+
|
31
|
+
# Backtrack state
|
32
|
+
#
|
33
|
+
def backtrack_state(snapshot)
|
34
|
+
super
|
35
|
+
snapshot.add(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set to browse-only (no answer will be given)
|
39
|
+
#
|
40
|
+
def browse(value=true)
|
41
|
+
@browse = (value && true)
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# True if in browser-only mode
|
46
|
+
#
|
47
|
+
def browse?
|
48
|
+
@browse
|
49
|
+
end
|
50
|
+
|
51
|
+
# True if the given date is the currently selected month
|
52
|
+
#
|
53
|
+
def current_month?(date)
|
54
|
+
Date.new(date.year, date.month, 1) == @month
|
55
|
+
end
|
56
|
+
|
57
|
+
# True if the given date is the currently selected day
|
58
|
+
#
|
59
|
+
def selected_day?(date)
|
60
|
+
date == @day
|
61
|
+
end
|
62
|
+
|
63
|
+
# Date object representing the previous month
|
64
|
+
#
|
65
|
+
def prev_month
|
66
|
+
@month << 1
|
67
|
+
end
|
68
|
+
|
69
|
+
# Date object representing the next month
|
70
|
+
#
|
71
|
+
def next_month
|
72
|
+
@month >> 1
|
73
|
+
end
|
74
|
+
|
75
|
+
# String to be displayed as the month heading
|
76
|
+
#
|
77
|
+
def month_heading
|
78
|
+
@month.strftime('%B %Y')
|
79
|
+
end
|
80
|
+
|
81
|
+
# String to be displayed indicating the current date
|
82
|
+
#
|
83
|
+
def today_string
|
84
|
+
Date.today.strftime('Today is %A, %b %d %Y')
|
85
|
+
end
|
86
|
+
|
87
|
+
# Render a given day
|
88
|
+
#
|
89
|
+
def render_day(date)
|
90
|
+
if current_month?(date)
|
91
|
+
selected_day?(date) ? render_selected_day(date) : render_month_day(date)
|
92
|
+
else
|
93
|
+
render_other_day(date)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Render a day of the currently selected month
|
98
|
+
#
|
99
|
+
def render_month_day(date)
|
100
|
+
r.table_data { r.anchor.callback { save(date) }.with(date.day) }
|
101
|
+
end
|
102
|
+
|
103
|
+
# Render the currently selected day
|
104
|
+
#
|
105
|
+
def render_selected_day(date)
|
106
|
+
r.table_data.style('border: 1px solid black').with do
|
107
|
+
r.anchor.style('font-weight: bold').callback { save(date) }.with(date.day)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Render days of the previous or next month
|
112
|
+
#
|
113
|
+
def render_other_day(date)
|
114
|
+
r.table_data do
|
115
|
+
r.anchor.style('color: silver').callback { save(date) }.with(date.day)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Render CSS styles
|
120
|
+
#
|
121
|
+
def render_styles
|
122
|
+
r.link.type('text/css').rel('stylesheet').href('/nemo/calendar.css')
|
123
|
+
end
|
124
|
+
|
125
|
+
# Render Calender header
|
126
|
+
#
|
127
|
+
def render_header
|
128
|
+
r.table_row do
|
129
|
+
r.table_header.colspan(4).with { r.encoded_text(month_heading) }
|
130
|
+
r.table_header { r.anchor.callback { go_prev }.with(prev_month.strftime('%b')) }
|
131
|
+
r.table_header { r.anchor.callback { go_next }.with(next_month.strftime('%b')) }
|
132
|
+
r.table_header { browse? ? r.space : r.anchor.callback { back }.style('color: black').with('X') }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Render Calendar footer
|
137
|
+
#
|
138
|
+
def render_footer
|
139
|
+
r.table_row { r.table_header.colspan(7).with { r.encoded_text(today_string) } }
|
140
|
+
end
|
141
|
+
|
142
|
+
# Render Calendar
|
143
|
+
#
|
144
|
+
def render
|
145
|
+
r.html do
|
146
|
+
r.head { r.title('Calendar'); render_styles }
|
147
|
+
r.body do
|
148
|
+
r.text(sprintf('<!--Month: %s, Day: %s-->', @month, @day))
|
149
|
+
r.table { r.table_row { r.table_header {
|
150
|
+
r.table do
|
151
|
+
render_header
|
152
|
+
r.table_row { Date::ABBR_DAYNAMES.each { |day| r.table_header(day) } }
|
153
|
+
@month.calendar.each do |week|
|
154
|
+
r.table_row do
|
155
|
+
week.each { |day| render_day(day) }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
render_footer
|
159
|
+
end
|
160
|
+
}}}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Return without changes
|
166
|
+
#
|
167
|
+
def back
|
168
|
+
answer nil unless browse?
|
169
|
+
end
|
170
|
+
|
171
|
+
# Select the previous month
|
172
|
+
#
|
173
|
+
def go_prev
|
174
|
+
@month = prev_month
|
175
|
+
end
|
176
|
+
|
177
|
+
# Select the next month
|
178
|
+
#
|
179
|
+
def go_next
|
180
|
+
@month = next_month
|
181
|
+
end
|
182
|
+
|
183
|
+
# Save the given day
|
184
|
+
#
|
185
|
+
def save(day)
|
186
|
+
@day = day
|
187
|
+
@month = Date.new(day.year, day.month, 1)
|
188
|
+
answer(day) unless browse?
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Confirmation dialog box
|
2
|
+
#
|
3
|
+
# Displays a message with OK(true) and Cancel(false) buttons.
|
4
|
+
#
|
5
|
+
class Nemo::Components::ConfirmDialog < Wee::Component
|
6
|
+
|
7
|
+
attr_accessor :text
|
8
|
+
|
9
|
+
def initialize(text)
|
10
|
+
super()
|
11
|
+
@text = text
|
12
|
+
end
|
13
|
+
|
14
|
+
def render
|
15
|
+
r.html do
|
16
|
+
r.head { r.title('Confirm') }
|
17
|
+
r.body.style('text-align: center').with do
|
18
|
+
r.break
|
19
|
+
r.encode_text(@text)
|
20
|
+
r.form do
|
21
|
+
r.submit_button.value('OK').callback{ answer true }
|
22
|
+
r.space
|
23
|
+
r.submit_button.value('Cancel').callback{ answer false }
|
24
|
+
end
|
25
|
+
r.break
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# Multiple Attribute container
|
2
|
+
#
|
3
|
+
# Interface to Add/Edit/Delete/View the related data of the given attribute.
|
4
|
+
#
|
5
|
+
class Nemo::Components::MultipleAttributeContainer < Wee::Component
|
6
|
+
|
7
|
+
attr_accessor :attribute, :components
|
8
|
+
|
9
|
+
def initialize(a)
|
10
|
+
super()
|
11
|
+
@attribute = a
|
12
|
+
@components = Array.new
|
13
|
+
a.cache.each { |obj| @components << viewer_for(obj.metaobject) }
|
14
|
+
@components << create_empty_editor
|
15
|
+
end
|
16
|
+
|
17
|
+
def children
|
18
|
+
@components
|
19
|
+
end
|
20
|
+
|
21
|
+
def backtrack_state(snapshot)
|
22
|
+
super
|
23
|
+
snapshot.add(self.children)
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(component)
|
27
|
+
@attribute.remove_from_cache(component.metaobject.baseobject)
|
28
|
+
@components.delete(component)
|
29
|
+
@components.pop
|
30
|
+
@components << create_empty_editor
|
31
|
+
end
|
32
|
+
|
33
|
+
def switch_to_edit_mode(viewer)
|
34
|
+
viewer.viewer = false
|
35
|
+
viewer.call( editor_for(viewer.metaobject) )
|
36
|
+
viewer.viewer = true
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_empty_editor
|
40
|
+
metaobject = @attribute.base_class.new.metaobject
|
41
|
+
if @components.size < @attribute.item_limit
|
42
|
+
editor = editor_for(metaobject)
|
43
|
+
answer = Wee::AnswerDecoration.new
|
44
|
+
answer.on_answer = method(:on_edit)
|
45
|
+
editor.add_decoration(answer)
|
46
|
+
editor
|
47
|
+
else
|
48
|
+
Nemo::Visitors::Visitor.new(metaobject) # placeholder
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def on_edit(obj)
|
53
|
+
if obj.nil?
|
54
|
+
@components.pop
|
55
|
+
@components << create_empty_editor
|
56
|
+
else
|
57
|
+
@attribute.add_to_cache(obj)
|
58
|
+
@components.pop
|
59
|
+
@components << viewer_for(obj.metaobject)
|
60
|
+
@components << create_empty_editor
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def editor_for(metaobject)
|
65
|
+
obj = Nemo::Visitors::Editor.new(metaobject)
|
66
|
+
obj.subform = true
|
67
|
+
obj
|
68
|
+
end
|
69
|
+
|
70
|
+
def viewer_for(metaobject)
|
71
|
+
Nemo::Visitors::Viewer.new(metaobject)
|
72
|
+
end
|
73
|
+
|
74
|
+
def render
|
75
|
+
@components.each do |component|
|
76
|
+
if component.viewer?
|
77
|
+
r.render(component)
|
78
|
+
r.submit_button.value('edit').callback{ switch_to_edit_mode(component) }
|
79
|
+
if @attribute.item_delete?
|
80
|
+
r.space
|
81
|
+
r.submit_button.value('delete').callback { delete(component) }
|
82
|
+
end
|
83
|
+
if @attribute.item_limit > 1
|
84
|
+
r.break
|
85
|
+
r.break
|
86
|
+
end
|
87
|
+
else
|
88
|
+
r.render(component)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Demonstrates the MiniCalendar component
|
2
|
+
#
|
3
|
+
module Nemo::Examples::DatePicker; end
|
4
|
+
|
5
|
+
# Set the value of a text field to a date string using a MiniCalendar.
|
6
|
+
#
|
7
|
+
class Nemo::Examples::DatePicker::Root < Wee::Component
|
8
|
+
|
9
|
+
# Holds the currently chosen date
|
10
|
+
attr_accessor :date
|
11
|
+
|
12
|
+
# Initialize with a Date object (defaults to today)
|
13
|
+
#
|
14
|
+
def initialize(date=Date.today)
|
15
|
+
super()
|
16
|
+
@date = date
|
17
|
+
end
|
18
|
+
|
19
|
+
# Backtrack state
|
20
|
+
#
|
21
|
+
def backtrack_state(snapshot)
|
22
|
+
super
|
23
|
+
snapshot.add(self)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Render CSS styles
|
27
|
+
#
|
28
|
+
def render_styles
|
29
|
+
r.link.type('text/css').rel('stylesheet').href('/nemo/calendar.css')
|
30
|
+
end
|
31
|
+
|
32
|
+
# Render date field, allow user to choose a Date using a MiniCalendar
|
33
|
+
#
|
34
|
+
def render
|
35
|
+
super
|
36
|
+
r.html do
|
37
|
+
r.head { r.title('Calendar Demo'); render_styles }
|
38
|
+
r.body do
|
39
|
+
r.form do
|
40
|
+
r.table { r.table_row { r.table_header {
|
41
|
+
r.table do
|
42
|
+
r.table_row { r.table_header('Calendar Demo') }
|
43
|
+
r.table_row { r.table_data {
|
44
|
+
r.text_input.value(@date.strftime('%b %d, %Y')).callback { |input| @date = begin Date.parse(input) rescue Date.today end }
|
45
|
+
r.space
|
46
|
+
r.nemo_calendar_button.callback { calendar }
|
47
|
+
}}
|
48
|
+
end
|
49
|
+
}}}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Call the calendar component
|
56
|
+
#
|
57
|
+
def calendar
|
58
|
+
if date = call( Nemo::Components::MiniCalendar.new(@date) )
|
59
|
+
@date = date
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
class Nemo::Examples::DatePicker::Session < Wee::Session
|
66
|
+
|
67
|
+
def initialize
|
68
|
+
super do
|
69
|
+
self.root_component = Nemo::Examples::DatePicker::Root.new
|
70
|
+
self.page_store = Wee::Utils::LRUCache.new(25) # backtrack up to 25 pages
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|