togo 0.4.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +210 -0
- data/lib/togo/admin/admin.rb +6 -25
- data/lib/togo/admin/helpers.rb +57 -0
- data/lib/togo/admin/public/css/screen.css +329 -190
- data/lib/togo/admin/public/img/arrow-down.png +0 -0
- data/lib/togo/admin/public/img/arrows.png +0 -0
- data/lib/togo/admin/public/img/bg-header.png +0 -0
- data/lib/togo/admin/public/img/button-bg-delete.png +0 -0
- data/lib/togo/admin/public/img/button-bg-save.png +0 -0
- data/lib/togo/admin/public/img/footer-bg.png +0 -0
- data/lib/togo/admin/public/img/subhead-bg.png +0 -0
- data/lib/togo/admin/public/js/edit.js +23 -24
- data/lib/togo/admin/public/js/index.js +3 -2
- data/lib/togo/admin/public/js/togo.js +46 -14
- data/lib/togo/admin/views/_paging.erb +14 -0
- data/lib/togo/admin/views/edit.erb +6 -9
- data/lib/togo/admin/views/index.erb +44 -44
- data/lib/togo/admin/views/layout.erb +14 -16
- data/lib/togo/admin/views/new.erb +5 -2
- data/lib/togo/model/model.rb +20 -38
- data/lib/togo/model/relationship_manager/many_to_one.rb +19 -0
- data/lib/togo/model/relationship_manager/one_to_many.rb +19 -0
- data/lib/togo/model/relationship_manager/relationship_manager.rb +40 -0
- data/lib/togo/model/types/belongs_to.erb +42 -40
- data/lib/togo/model/types/has_n.erb +44 -42
- data/lib/togo/model/types/many_to_many.erb +44 -42
- metadata +17 -7
- data/README +0 -29
data/README.md
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
# Introducing Togo
|
2
|
+
|
3
|
+
Togo is a CMS framework. It also includes a simple web framework based on Rack that can be used to make micro-webapps (similar to Sinatra).
|
4
|
+
However the goal of Togo is to be a modular system that can be used with any Ruby ORM framework, giving you a complete CMS system for free.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
gem install togo
|
9
|
+
|
10
|
+
## Tutorial
|
11
|
+
|
12
|
+
Togo works by a simply including a line in your model definition (currently, Togo only works for DataMapper). By including Togo in your model,
|
13
|
+
it will be automatically integrated into the Togo admin application, giving you all the CRUD actions as well as listing and search. It even
|
14
|
+
works with associations.
|
15
|
+
|
16
|
+
### Setting up a simple application
|
17
|
+
|
18
|
+
We'll use a combination of Sinatra, DataMapper and Sqlite for our tutorial. You actually don't need Sinatra to run the Togo admin application, however this
|
19
|
+
gives a good idea of how Togo can be used in conjuction with an existing project.
|
20
|
+
|
21
|
+
You'll need the following gems installed:
|
22
|
+
|
23
|
+
pre. dm-core dm-serializer sinatra dm-sqlite3-adapter
|
24
|
+
|
25
|
+
Next, make a directory for your project like so:
|
26
|
+
|
27
|
+
|
28
|
+
togo-example/
|
29
|
+
|
|
30
|
+
-- models/
|
31
|
+
|
|
32
|
+
-- views/
|
33
|
+
|
|
34
|
+
-- init.rb
|
35
|
+
|
|
36
|
+
-- app.rb
|
37
|
+
|
|
38
|
+
-- example.db
|
39
|
+
|
40
|
+
|
41
|
+
### Set up your models
|
42
|
+
|
43
|
+
Inside the models directory, create a blog_entry.rb and comment.rb file, and drop in the following:
|
44
|
+
|
45
|
+
|
46
|
+
#blog_entry.rb:
|
47
|
+
|
48
|
+
class BlogEntry
|
49
|
+
|
50
|
+
include DataMapper::Resource
|
51
|
+
include Togo::DataMapper::Model
|
52
|
+
|
53
|
+
property :id, Serial
|
54
|
+
property :title, String
|
55
|
+
property :body, Text
|
56
|
+
property :date, DateTime
|
57
|
+
|
58
|
+
has n, :comments
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
#comment.rb:
|
63
|
+
|
64
|
+
class Comment
|
65
|
+
include DataMapper::Resource
|
66
|
+
include Togo::DataMapper::Model
|
67
|
+
|
68
|
+
property :id, Serial
|
69
|
+
property :name, String
|
70
|
+
property :email, String
|
71
|
+
property :body, Text
|
72
|
+
property :date, DateTime
|
73
|
+
property :blog_entry_id, Integer
|
74
|
+
|
75
|
+
belongs_to :blog_entry
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
### Create an init file
|
80
|
+
|
81
|
+
Togo doesn't set up your database connection or initialize anything needed by your models for you, so you'll have to tell
|
82
|
+
it what to do. There are a couple ways of doing it: put the code in a file of your choice and tell Togo to include
|
83
|
+
it at runtime, or place the code in a file called togo-admin-config.rb in the same directory that you will run Togo from.
|
84
|
+
|
85
|
+
In this example, we'll put our initialization in the init.rb file and tell Togo to include it at boot time.
|
86
|
+
|
87
|
+
|
88
|
+
#init.rb:
|
89
|
+
|
90
|
+
SITE_ROOT = File.dirname(File.expand_path(__FILE__))
|
91
|
+
%w(dm-core dm-migrations togo).each{|l| require l}
|
92
|
+
Dir.glob(File.join(SITE_ROOT,'models','*.rb')).each{|f| require f}
|
93
|
+
DataMapper::Logger.new(STDOUT, :debug)
|
94
|
+
DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/example.db")
|
95
|
+
DataMapper.auto_upgrade!
|
96
|
+
|
97
|
+
|
98
|
+
### You're ready to start using Togo
|
99
|
+
|
100
|
+
At this point you can fire up the Togo admin application. Remember we have to include our init.rb file:
|
101
|
+
|
102
|
+
togo-admin -r init.rb
|
103
|
+
|
104
|
+
Open up http://0.0.0.0:8080 in your browser and have a blast! Have a look at our CMS User's Guide for a walkthrough of base CMS functionality (Coming Soon).
|
105
|
+
|
106
|
+
## Customizing Togo
|
107
|
+
|
108
|
+
Now you have a complete CMS system for free, but you can also customize what fields are used, what order they appear, and even
|
109
|
+
use your own templates for complete customization.
|
110
|
+
|
111
|
+
### Telling Togo which fields to use
|
112
|
+
|
113
|
+
By default Togo will look at your model and include all available fields in the admin, but you can also tell it specifically which fields
|
114
|
+
to use for both the list view and form view.
|
115
|
+
|
116
|
+
#### Changing the list view
|
117
|
+
|
118
|
+
If you only want the title and date field to appear in the list view of blog entries, just add in to your model definition:
|
119
|
+
|
120
|
+
|
121
|
+
class BlogEntry
|
122
|
+
|
123
|
+
include DataMapper::Resource
|
124
|
+
include Togo::DataMapper::Model
|
125
|
+
... snipped ...
|
126
|
+
|
127
|
+
list_properties :title, :date
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
Togo will display the fields in the order given. So if you wanted date first some reason, you could just do
|
133
|
+
|
134
|
+
pre. list_properties :date, :title
|
135
|
+
|
136
|
+
And date will be listed in the first column.
|
137
|
+
|
138
|
+
#### Changing the form view (New and Edit views)
|
139
|
+
|
140
|
+
Just as you can change the list view fields, you can also tell Togo which fields to show in form view and in which order.
|
141
|
+
|
142
|
+
|
143
|
+
class BlogEntry
|
144
|
+
|
145
|
+
include DataMapper::Resource
|
146
|
+
include Togo::DataMapper::Model
|
147
|
+
... snipped ...
|
148
|
+
|
149
|
+
list_properties :title, :date
|
150
|
+
form_properties :title, :date, :body
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
Associations also can be used in list and form property declarations:
|
156
|
+
|
157
|
+
|
158
|
+
class BlogEntry
|
159
|
+
|
160
|
+
include DataMapper::Resource
|
161
|
+
include Togo::DataMapper::Model
|
162
|
+
... snipped ...
|
163
|
+
|
164
|
+
list_properties :title, :date
|
165
|
+
form_properties :title, :date, :body, :comments
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
#### Configuring properties
|
171
|
+
|
172
|
+
There are some options you can change on each field individually by using the configure_property declaration.
|
173
|
+
|
174
|
+
Here you can customize the label:
|
175
|
+
|
176
|
+
class BlogEntry
|
177
|
+
|
178
|
+
include DataMapper::Resource
|
179
|
+
include Togo::DataMapper::Model
|
180
|
+
... snipped ...
|
181
|
+
|
182
|
+
list_properties :title, :date
|
183
|
+
form_properties :title, :date, :body
|
184
|
+
|
185
|
+
configure_property :title, :label => "Title (keep it short and sweet)"
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
Each property type has it's own default form template that can be overridden simply by specifying a full path to the template (only ERB is currently supported):
|
191
|
+
|
192
|
+
|
193
|
+
class BlogEntry
|
194
|
+
|
195
|
+
include DataMapper::Resource
|
196
|
+
include Togo::DataMapper::Model
|
197
|
+
... snipped ...
|
198
|
+
|
199
|
+
list_properties :title, :date
|
200
|
+
form_properties :title, :date, :body
|
201
|
+
|
202
|
+
configure_property :title, :label => "Title (keep it short and sweet)"
|
203
|
+
configure_property :body, :template => File.join(SITE_ROOT, 'views', 'custom_body.erb')
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
Note we used the SITE_ROOT constant we defined in the init.rb file, how you get the full path to your template may vary depending on your setup.
|
209
|
+
|
210
|
+
See the Writing a Form Template Guide for more information. (Coming Soon)
|
data/lib/togo/admin/admin.rb
CHANGED
@@ -1,24 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
def paging_links(page, count, qs = {})
|
4
|
-
prev_link, next_link = 'Previous', 'Next'
|
5
|
-
if not qs.blank?
|
6
|
-
qs = qs.keys.collect{|k|
|
7
|
-
[k,escape(qs[k])].join('=') if not qs[k].blank?
|
8
|
-
}.compact.join('&')
|
9
|
-
qs = nil if qs.blank?
|
10
|
-
end
|
11
|
-
|
12
|
-
if not page == 1
|
13
|
-
prev_link = "<a href=\"?p=#{[page-1, qs].compact.join('&')}\" rel=\"previous\">#{prev_link}</a>"
|
14
|
-
end
|
15
|
-
if not page == count and count > 1
|
16
|
-
next_link = "<a href=\"?p=#{[page+1, qs].compact.join('&')}\" rel=\"next\">#{next_link}</a>"
|
17
|
-
end
|
18
|
-
[prev_link, next_link]
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
1
|
+
require 'helpers'
|
22
2
|
|
23
3
|
module Togo
|
24
4
|
class Admin < Dispatch
|
@@ -38,9 +18,10 @@ module Togo
|
|
38
18
|
@p = (params[:p] || 1).to_i
|
39
19
|
@limit = 50
|
40
20
|
@offset = @limit*(@p-1)
|
21
|
+
@order = (params[:o] || "id.desc").split('.').map(&:to_sym)
|
41
22
|
@count = (@q.blank? ? @model.all : @model.search(:q => @q)).size
|
42
23
|
@page_count = @count == 0 ? 1 : (@count.to_f/@limit.to_f).ceil
|
43
|
-
@criteria = {:limit => @limit, :offset => @offset}
|
24
|
+
@criteria = {:limit => @limit, :offset => @offset, :order => @order[0].send(@order[1])}
|
44
25
|
@content = @q.blank? ? @model.all(@criteria) : @model.search(@criteria.merge(:q => @q))
|
45
26
|
erb :index
|
46
27
|
end
|
@@ -54,7 +35,7 @@ module Togo
|
|
54
35
|
@content = @model.stage_content(@model.new,params)
|
55
36
|
begin
|
56
37
|
raise "Could not save content" if not @content.save
|
57
|
-
redirect "/#{@model.name}"
|
38
|
+
redirect params[:return_url] || "/#{@model.name}"
|
58
39
|
rescue => detail
|
59
40
|
@errors = detail.to_s
|
60
41
|
erb :new
|
@@ -70,7 +51,7 @@ module Togo
|
|
70
51
|
@content = @model.stage_content(@model.get(params[:id]),params)
|
71
52
|
begin
|
72
53
|
raise "Could not save content" if not @content.save
|
73
|
-
redirect "/#{@model.name}"
|
54
|
+
redirect params[:return_url] || "/#{@model.name}"
|
74
55
|
rescue => detail
|
75
56
|
@errors = detail.to_s
|
76
57
|
erb :edit
|
@@ -83,7 +64,7 @@ module Togo
|
|
83
64
|
@items.each do |i|
|
84
65
|
@model.delete_content(i)
|
85
66
|
end
|
86
|
-
redirect "/#{@model.name}"
|
67
|
+
redirect params[:return_url] || "/#{@model.name}"
|
87
68
|
rescue => detail
|
88
69
|
@errors = detail.to_s
|
89
70
|
@content = params[:q] ? @model.search(:q => params[:q]) : @model.all
|
data/lib/togo/admin/helpers.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
module Helpers
|
2
|
+
|
3
|
+
class FlashHash
|
4
|
+
def initialize
|
5
|
+
@h = {}
|
6
|
+
@c = {}
|
7
|
+
end
|
8
|
+
def [](key)
|
9
|
+
return @c[key] if @c.keys.include?(key)
|
10
|
+
@c[key] = @h.delete(key) if @h.keys.include?(key)
|
11
|
+
end
|
12
|
+
def []=(key,val)
|
13
|
+
@h[key] = val
|
14
|
+
end
|
15
|
+
def sweep!
|
16
|
+
@c = {}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def hash_to_qs(h)
|
21
|
+
return nil if h.blank?
|
22
|
+
qs = h.keys.collect{|k|
|
23
|
+
[k,escape(h[k])].join('=') if not h[k].blank?
|
24
|
+
}.compact.join('&')
|
25
|
+
qs = nil if qs.blank?
|
26
|
+
qs
|
27
|
+
end
|
28
|
+
|
29
|
+
def paging_links(page, count, qs = {})
|
30
|
+
prev_link, next_link = 'Previous', 'Next'
|
31
|
+
qs = hash_to_qs(qs)
|
32
|
+
|
33
|
+
if not page == 1
|
34
|
+
prev_link = "<a href=\"?p=#{[page-1, qs].compact.join('&')}\" rel=\"previous\">#{prev_link}</a>"
|
35
|
+
end
|
36
|
+
if not page == count and count > 1
|
37
|
+
next_link = "<a href=\"?p=#{[page+1, qs].compact.join('&')}\" rel=\"next\">#{next_link}</a>"
|
38
|
+
end
|
39
|
+
[prev_link, next_link]
|
40
|
+
end
|
41
|
+
|
42
|
+
def column_head_link(property, current_order, qs = {})
|
43
|
+
qs = hash_to_qs(qs)
|
44
|
+
new_order = (current_order[0] == property.name.to_sym ? (current_order[1] == :asc ? "desc" : "asc") : "asc")
|
45
|
+
link_class = current_order[0] == property.name.to_sym ? new_order : ''
|
46
|
+
"<a href=\"?o=#{[(property.name.to_s+'.'+new_order.to_s),qs].compact.join('&')}\" class=\"#{link_class}\">#{property.name.to_s.humanize.titleize}</a>"
|
47
|
+
end
|
48
|
+
|
49
|
+
def partial(template, options={})
|
50
|
+
erb template, options.merge(:layout => false)
|
51
|
+
end
|
52
|
+
|
53
|
+
def active_menu(name)
|
54
|
+
request.path =~ /#{name}/ ? 'active' : ''
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|