trellis 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +6 -0
- data/examples/crud_components/html/address_view_edit.xhtml +3 -1
- data/examples/crud_components/html/addresses.xhtml +3 -1
- data/examples/flickr/source/flickr.rb +1 -1
- data/examples/guest_book/source/guest_book.rb +2 -1
- data/examples/hangman/html/game_over.xhtml +3 -1
- data/examples/hangman/html/guess.xhtml +3 -1
- data/examples/hangman/html/start.xhtml +3 -1
- data/examples/hilo/html/game_over.xhtml +3 -1
- data/examples/hilo/html/guess.xhtml +3 -1
- data/examples/hilo/html/start.xhtml +3 -1
- data/examples/routing/source/routing.rb +1 -1
- data/examples/simplest/source/simplest.rb +1 -1
- data/examples/stateful_counters/html/counters.xhtml +4 -2
- data/lib/trellis/component_library/core_components.rb +19 -3
- data/lib/trellis/trellis.rb +284 -48
- data/lib/trellis/utils.rb +22 -0
- data/lib/trellis/version.rb +1 -1
- data/test/application_spec.rb +150 -41
- data/test/component_spec.rb +4 -4
- data/test/core_extensions_spec.rb +27 -5
- data/test/fixtures/application_spec_applications.rb +220 -13
- data/test/fixtures/component_spec_components.rb +4 -4
- data/test/page_spec.rb +109 -36
- data/test/renderer_spec.rb +3 -3
- data/test/router_spec.rb +2 -0
- metadata +2 -2
data/History.txt
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
== 0.0.7
|
2
|
+
* 3 major enhancement:
|
3
|
+
* implemented page get method to override default page rendering
|
4
|
+
* added trellis namespace xmlns method for markaby (to prevent rexml from freaking out)
|
5
|
+
* clean all examples to add trellis namespace
|
6
|
+
|
1
7
|
== 0.0.6
|
2
8
|
* 2 major enhancement:
|
3
9
|
* added dup.call for thread safety
|
@@ -5,7 +5,9 @@
|
|
5
5
|
Author : bsbodden
|
6
6
|
-->
|
7
7
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
8
|
-
<html
|
8
|
+
<html xml:lang="en" lang="en"
|
9
|
+
xmlns:trellis="http://trellisframework.org/schema/trellis_1_0_0.xsd"
|
10
|
+
xmlns="http://www.w3.org/1999/xhtml">
|
9
11
|
<head>
|
10
12
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
11
13
|
<link rel="stylesheet" type="text/css" href="/style/trellis.css" />
|
@@ -5,7 +5,9 @@
|
|
5
5
|
Author : bsbodden
|
6
6
|
-->
|
7
7
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
8
|
-
<html
|
8
|
+
<html xml:lang="en" lang="en"
|
9
|
+
xmlns:trellis="http://trellisframework.org/schema/trellis_1_0_0.xsd"
|
10
|
+
xmlns="http://www.w3.org/1999/xhtml">
|
9
11
|
<head>
|
10
12
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
11
13
|
<link rel="stylesheet" type="text/css" href="/style/trellis.css" />
|
@@ -36,7 +36,8 @@ module GuestBookApp
|
|
36
36
|
template(%[
|
37
37
|
!!! XML
|
38
38
|
!!! Strict
|
39
|
-
%html{ :xmlns => "http://www.w3.org/1999/xhtml"
|
39
|
+
%html{ :xmlns => "http://www.w3.org/1999/xhtml",
|
40
|
+
"xmlns:trellis" => "http://trellisframework.org/schema/trellis_1_0_0.xsd" }
|
40
41
|
%head
|
41
42
|
%title
|
42
43
|
Trellis Guest Book
|
@@ -1,6 +1,8 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
2
2
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
-
<html
|
3
|
+
<html xml:lang="en" lang="en"
|
4
|
+
xmlns:trellis="http://trellisframework.org/schema/trellis_1_0_0.xsd"
|
5
|
+
xmlns="http://www.w3.org/1999/xhtml">
|
4
6
|
<head>
|
5
7
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
6
8
|
<link rel="stylesheet" type="text/css" href="/style/hangman.css" />
|
@@ -1,6 +1,8 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
2
2
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
-
<html
|
3
|
+
<html xml:lang="en" lang="en"
|
4
|
+
xmlns:trellis="http://trellisframework.org/schema/trellis_1_0_0.xsd"
|
5
|
+
xmlns="http://www.w3.org/1999/xhtml">
|
4
6
|
<head>
|
5
7
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
6
8
|
<link rel="stylesheet" type="text/css" href="/style/hangman.css" />
|
@@ -1,6 +1,8 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
2
2
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
-
<html
|
3
|
+
<html xml:lang="en" lang="en"
|
4
|
+
xmlns:trellis="http://trellisframework.org/schema/trellis_1_0_0.xsd"
|
5
|
+
xmlns="http://www.w3.org/1999/xhtml">
|
4
6
|
<head>
|
5
7
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
6
8
|
<link rel="stylesheet" type="text/css" href="/style/hangman.css" />
|
@@ -1,6 +1,8 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
2
2
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
-
<html
|
3
|
+
<html xml:lang="en" lang="en"
|
4
|
+
xmlns:trellis="http://trellisframework.org/schema/trellis_1_0_0.xsd"
|
5
|
+
xmlns="http://www.w3.org/1999/xhtml">
|
4
6
|
<head>
|
5
7
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
6
8
|
<title>Game Over!</title>
|
@@ -1,6 +1,8 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
2
2
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
-
<html
|
3
|
+
<html xml:lang="en" lang="en"
|
4
|
+
xmlns:trellis="http://trellisframework.org/schema/trellis_1_0_0.xsd"
|
5
|
+
xmlns="http://www.w3.org/1999/xhtml">
|
4
6
|
<head>
|
5
7
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
6
8
|
<title>Guess A Number</title>
|
@@ -1,6 +1,8 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
2
2
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
-
<html
|
3
|
+
<html xml:lang="en" lang="en"
|
4
|
+
xmlns:trellis="http://trellisframework.org/schema/trellis_1_0_0.xsd"
|
5
|
+
xmlns="http://www.w3.org/1999/xhtml">
|
4
6
|
<head>
|
5
7
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
6
8
|
<title>Hi/Lo Game Start Page</title>
|
@@ -1,6 +1,8 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
2
2
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
-
<html lang="en"
|
3
|
+
<html xml:lang="en" lang="en"
|
4
|
+
xmlns:trellis="http://trellisframework.org/schema/trellis_1_0_0.xsd"
|
5
|
+
xmlns="http://www.w3.org/1999/xhtml">
|
4
6
|
<head>
|
5
7
|
<title>Trellis: Multi Counter</title>
|
6
8
|
<link rel="stylesheet" type="text/css" media="all" href="style/main.css"/>
|
@@ -46,4 +48,4 @@
|
|
46
48
|
</div>
|
47
49
|
</div>
|
48
50
|
</body>
|
49
|
-
</html>
|
51
|
+
</html>
|
@@ -230,13 +230,29 @@ module Trellis
|
|
230
230
|
on_behalf = tag.attr['on_behalf']
|
231
231
|
method = tag.attr['method'] || 'GET'
|
232
232
|
tag.locals.form_name = form_name
|
233
|
-
|
233
|
+
value = tag.attr['value']
|
234
|
+
|
235
|
+
if value
|
236
|
+
eval_value = tag.locals.instance_eval(value) || tag.globals.instance_eval(value)
|
237
|
+
end
|
238
|
+
|
239
|
+
path = (tag.globals.page.path.nil? || tag.globals.page.path.empty?) ? nil : tag.globals.page.path
|
240
|
+
class_name = tag.globals.page.class.name
|
241
|
+
target_page = path || class_name
|
242
|
+
|
234
243
|
href = Trellis::DefaultRouter.to_uri(:url_root => url_root,
|
235
244
|
:page => target_page,
|
236
245
|
:event => "submit",
|
237
|
-
:source => "#{(on_behalf ? on_behalf : form_name)}"
|
246
|
+
:source => "#{(on_behalf ? on_behalf : form_name)}",
|
247
|
+
:value => eval_value)
|
248
|
+
|
249
|
+
attrs = tag.attr.exclude_keys('tid', 'on_behalf', 'method')
|
250
|
+
attrs["name"] = form_name
|
251
|
+
attrs["action"] = href
|
252
|
+
attrs["method"] = method
|
253
|
+
|
238
254
|
builder = Builder::XmlMarkup.new
|
239
|
-
builder.form(
|
255
|
+
builder.form(attrs) do |form|
|
240
256
|
form << tag.expand
|
241
257
|
end
|
242
258
|
end
|
data/lib/trellis/trellis.rb
CHANGED
@@ -30,8 +30,7 @@ require 'rubygems'
|
|
30
30
|
require 'rack'
|
31
31
|
require 'radius'
|
32
32
|
require 'builder'
|
33
|
-
require '
|
34
|
-
require 'rexml/document'
|
33
|
+
require 'nokogiri'
|
35
34
|
require 'extensions/string'
|
36
35
|
require 'haml'
|
37
36
|
require 'markaby'
|
@@ -43,6 +42,8 @@ require 'erubis'
|
|
43
42
|
require 'ostruct'
|
44
43
|
|
45
44
|
module Trellis
|
45
|
+
|
46
|
+
TEMPLATE_FORMATS = [:html, :xhtml, :haml, :textile, :markdown, :eruby]
|
46
47
|
|
47
48
|
# -- Application --
|
48
49
|
# Represents a Trellis Web Application. An application can define one or more
|
@@ -50,11 +51,16 @@ module Trellis
|
|
50
51
|
class Application
|
51
52
|
include Logging
|
52
53
|
include Rack::Utils
|
54
|
+
include Nokogiri::XML
|
55
|
+
|
56
|
+
@@partials = Hash.new
|
57
|
+
@@layouts = Hash.new
|
53
58
|
|
54
59
|
# descendant application classes get a singleton class level instances for
|
55
60
|
# holding homepage, dependent pages, static resource routing paths
|
56
61
|
def self.inherited(child) #:nodoc:
|
57
62
|
child.class_attr_reader(:homepage)
|
63
|
+
child.attr_array(:persistents)
|
58
64
|
child.class_attr_reader(:session_config)
|
59
65
|
child.attr_array(:static_routes)
|
60
66
|
child.meta_def(:logger) { Application.logger }
|
@@ -77,10 +83,33 @@ module Trellis
|
|
77
83
|
@static_routes << {:urls => urls, :root => root}
|
78
84
|
end
|
79
85
|
|
86
|
+
# application-wide persistent fields
|
87
|
+
def self.persistent(*fields)
|
88
|
+
instance_attr_accessor fields
|
89
|
+
@persistents = @persistents | fields
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.partials
|
93
|
+
@@partials
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.partial(name, body = nil, options = nil, &block)
|
97
|
+
store_template(name, :partial, body, options, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.layouts
|
101
|
+
@@layouts
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.layout(name, body = nil, options = nil, &block)
|
105
|
+
store_template(name, :layout, body, options, &block)
|
106
|
+
end
|
107
|
+
|
80
108
|
# bootstrap the application
|
81
109
|
def start(port = 3000)
|
82
110
|
Application.logger.info "Starting Trellis Application #{self.class} on port #{port}"
|
83
111
|
|
112
|
+
# only in development mode
|
84
113
|
directory_watcher = configure_directory_watcher
|
85
114
|
directory_watcher.start
|
86
115
|
|
@@ -99,7 +128,7 @@ module Trellis
|
|
99
128
|
# configure rack middleware
|
100
129
|
application = Rack::ShowStatus.new(self)
|
101
130
|
application = Rack::ShowExceptions.new(application)
|
102
|
-
application = Rack::Reloader.new(application)
|
131
|
+
application = Rack::Reloader.new(application) # only in development mode
|
103
132
|
application = Rack::CommonLogger.new(application, Application.logger)
|
104
133
|
|
105
134
|
# configure rack session
|
@@ -126,7 +155,7 @@ module Trellis
|
|
126
155
|
match ? match.router : DefaultRouter.new(:application => self)
|
127
156
|
end
|
128
157
|
|
129
|
-
#
|
158
|
+
# rack call interface.
|
130
159
|
def call(env)
|
131
160
|
dup.call!(env)
|
132
161
|
end
|
@@ -145,6 +174,9 @@ module Trellis
|
|
145
174
|
|
146
175
|
page = route.destination.new if route.destination
|
147
176
|
if page
|
177
|
+
load_persistent_fields_data(session)
|
178
|
+
page.application = self
|
179
|
+
|
148
180
|
page.class.url_root = request.script_name
|
149
181
|
page.path = request.path_info.sub(/^\//, '')
|
150
182
|
page.inject_dependent_pages
|
@@ -156,9 +188,16 @@ module Trellis
|
|
156
188
|
result = route.event ? page.process_event(route.event, route.value, route.source, session) : page
|
157
189
|
|
158
190
|
Application.logger.debug "response is #{result} an instance of #{result.class}"
|
159
|
-
|
191
|
+
|
192
|
+
# -------------------------
|
160
193
|
# prepare the http response
|
161
|
-
|
194
|
+
# -------------------------
|
195
|
+
|
196
|
+
if result.kind_of?(Trellis::Redirect)
|
197
|
+
# redirect short circuits
|
198
|
+
result.apply_to(request, response)
|
199
|
+
Application.logger.debug "redirecting to ==> #{request.script_name}/#{result.target}"
|
200
|
+
elsif (request.post? || route.event) && result.kind_of?(Trellis::Page)
|
162
201
|
# for action events of posts then use redirect after post pattern
|
163
202
|
# remove the events path and just return to the page
|
164
203
|
path = result.path ? result.path.gsub(/\/events\/.*/, '') : result.class.class_to_sym
|
@@ -166,17 +205,89 @@ module Trellis
|
|
166
205
|
response.headers["Location"] = "#{request.script_name}/#{path}"
|
167
206
|
Application.logger.debug "redirecting to ==> #{request.script_name}/#{path}"
|
168
207
|
else
|
169
|
-
#
|
170
|
-
|
171
|
-
|
208
|
+
# handle the get method
|
209
|
+
if result.kind_of?(Trellis::Page) && result.respond_to?(:get)
|
210
|
+
get = result.get
|
211
|
+
if get.kind_of?(Trellis::Redirect)
|
212
|
+
# redirect short circuits
|
213
|
+
get.apply_to(request, response)
|
214
|
+
Application.logger.debug "redirecting to ==> #{request.script_name}/#{get.target}"
|
215
|
+
elsif (get.class == result.class) || !get.kind_of?(Trellis::Page)
|
216
|
+
response.body = get.kind_of?(Trellis::Page) ? get.render : get
|
217
|
+
response.status = 200
|
218
|
+
else
|
219
|
+
path = get.path ? get.path.gsub(/\/events\/.*/, '') : get.class.class_to_sym
|
220
|
+
response.status = 302
|
221
|
+
response.headers["Location"] = "#{request.script_name}/#{path}"
|
222
|
+
Application.logger.debug "redirecting to ==> #{request.script_name}/#{path}"
|
223
|
+
end
|
224
|
+
else
|
225
|
+
# for render requests simply render the page
|
226
|
+
response.body = result.kind_of?(Trellis::Page) ? result.render : result
|
227
|
+
response.status = 200
|
228
|
+
end
|
172
229
|
end
|
173
230
|
else
|
174
231
|
response.status = 404
|
175
232
|
end
|
233
|
+
save_persistent_fields_data(session)
|
176
234
|
response.finish
|
177
235
|
end
|
178
236
|
|
179
237
|
private
|
238
|
+
|
239
|
+
def self.store_template(name, type, body = nil, options = nil, &block)
|
240
|
+
format = (options[:format] if options) || :html
|
241
|
+
if block_given?
|
242
|
+
mab = Markaby::Builder.new({}, self, &block)
|
243
|
+
html = mab.to_s
|
244
|
+
else
|
245
|
+
case format
|
246
|
+
when :haml
|
247
|
+
html = Haml::Engine.new(body).render
|
248
|
+
when :textile
|
249
|
+
html = RedCloth.new(body).to_html
|
250
|
+
when :markdown
|
251
|
+
if type == :partial
|
252
|
+
html = BlueCloth.new(body).to_html
|
253
|
+
else
|
254
|
+
html = Markaby.build { thtml { body { text "#{BlueCloth.new(body).to_html}" } }}
|
255
|
+
end
|
256
|
+
else # assume the body is (x)html, also eruby is treated as (x)html at this point
|
257
|
+
html = body
|
258
|
+
end
|
259
|
+
end
|
260
|
+
template = Nokogiri::XML(html)
|
261
|
+
case type
|
262
|
+
when :layout
|
263
|
+
@@layouts[name] = OpenStruct.new({:name => name,
|
264
|
+
:template => template,
|
265
|
+
:to_xml => template.to_xml,
|
266
|
+
:format => format})
|
267
|
+
when :partial
|
268
|
+
@@partials[name] = OpenStruct.new({:name => name,
|
269
|
+
:template => template,
|
270
|
+
:to_xml => template.to_xml(:save_with => Node::SaveOptions::NO_DECLARATION),
|
271
|
+
:format => format})
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def load_persistent_fields_data(session)
|
276
|
+
self.class.persistents.each do |persistent_field|
|
277
|
+
field = "@#{persistent_field}".to_sym
|
278
|
+
current_value = instance_variable_get(field)
|
279
|
+
new_value = session[persistent_field]
|
280
|
+
if current_value != new_value && new_value != nil
|
281
|
+
instance_variable_set(field, new_value)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def save_persistent_fields_data(session)
|
287
|
+
self.class.persistents.each do |persistent_field|
|
288
|
+
session[persistent_field] = instance_variable_get("@#{persistent_field}".to_sym)
|
289
|
+
end
|
290
|
+
end
|
180
291
|
|
181
292
|
def configure_directory_watcher(directory = nil)
|
182
293
|
# set directory watcher to reload templates
|
@@ -316,20 +427,47 @@ module Trellis
|
|
316
427
|
end
|
317
428
|
|
318
429
|
def self.to_uri(options={})
|
430
|
+
# get options
|
319
431
|
url_root = options[:url_root]
|
320
432
|
page = options[:page]
|
321
433
|
event = options[:event]
|
322
434
|
source = options[:source]
|
323
435
|
value = options[:value]
|
324
|
-
|
325
|
-
|
436
|
+
|
437
|
+
destination = page
|
438
|
+
url_root = "/"
|
439
|
+
|
440
|
+
if page.kind_of?(Trellis::Page)
|
441
|
+
destination = page.path || page.class.class_to_sym
|
442
|
+
root = page.class.url_root
|
443
|
+
url_root = (root && !root.empty?) ? "/#{root}" : '/'
|
444
|
+
end
|
445
|
+
|
326
446
|
source = source ? ".#{source}" : ''
|
327
447
|
value = value ? "/#{value}" : ''
|
328
448
|
event_info = event ? "/events/#{event}#{source}#{value}" : ''
|
449
|
+
|
329
450
|
"#{url_root}#{destination}#{event_info}"
|
330
451
|
end
|
331
452
|
end
|
332
453
|
|
454
|
+
# -- Redirect --
|
455
|
+
# Encapsulates an HTTP redirect (is the object returned by Page#redirect method)
|
456
|
+
class Redirect
|
457
|
+
attr_reader :target, :status
|
458
|
+
|
459
|
+
def initialize(target, status=nil)
|
460
|
+
status = 302 unless status
|
461
|
+
raise ArgumentError.new("#{status} is not a valid redirect status") unless status >= 300 && status < 400
|
462
|
+
@target, @status = target, status
|
463
|
+
end
|
464
|
+
|
465
|
+
def apply_to(request, response)
|
466
|
+
response.status = status
|
467
|
+
response["Location"] = "#{request.script_name}#{target.starts_with?('/') ? '' : '/'}#{target}"
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
333
471
|
# -- Page --
|
334
472
|
# Represents a Web Page in a Trellis Application. A Page can contain multiple
|
335
473
|
# components and it defines a template or view either as an external file
|
@@ -337,13 +475,12 @@ module Trellis
|
|
337
475
|
# A Trellis Page contains listener methods to respond to events trigger by
|
338
476
|
# components in the same page or other pages
|
339
477
|
class Page
|
340
|
-
|
341
|
-
TEMPLATE_FORMATS = [:html, :xhtml, :haml, :textile, :markdown, :eruby]
|
478
|
+
include Nokogiri::XML
|
342
479
|
|
343
480
|
@@subclasses = Hash.new
|
344
481
|
@@template_registry = Hash.new
|
345
482
|
|
346
|
-
attr_accessor :params, :path, :logger
|
483
|
+
attr_accessor :application, :params, :path, :logger
|
347
484
|
|
348
485
|
def self.inherited(child) #:nodoc:
|
349
486
|
sym = child.class_to_sym
|
@@ -357,15 +494,19 @@ module Trellis
|
|
357
494
|
child.class_attr_accessor :url_root
|
358
495
|
child.class_attr_accessor :name
|
359
496
|
child.class_attr_accessor :router
|
360
|
-
child.class_attr_accessor :layout
|
361
497
|
child.meta_def(:add_stateful_component) { |tid,clazz| @stateful_components << [tid,clazz] }
|
362
498
|
|
363
499
|
locate_template child
|
364
500
|
super
|
365
501
|
end
|
366
502
|
|
503
|
+
def self.layout
|
504
|
+
@layout
|
505
|
+
end
|
506
|
+
|
367
507
|
def self.template(body = nil, options = nil, &block)
|
368
508
|
@format = (options[:format] if options) || :html
|
509
|
+
@layout = (options[:layout] if options)
|
369
510
|
if block_given?
|
370
511
|
mab = Markaby::Builder.new({}, self, &block)
|
371
512
|
html = mab.to_s
|
@@ -376,16 +517,27 @@ module Trellis
|
|
376
517
|
when :textile
|
377
518
|
html = RedCloth.new(body).to_html
|
378
519
|
when :markdown
|
379
|
-
|
520
|
+
if @layout
|
521
|
+
html = BlueCloth.new(body).to_html
|
522
|
+
else
|
523
|
+
html = Markaby.build { thtml { body { text "#{BlueCloth.new(body).to_html}" } }}
|
524
|
+
end
|
380
525
|
else # assume the body is (x)html, also eruby is treated as (x)html at this point
|
381
526
|
html = body
|
382
527
|
end
|
383
528
|
end
|
384
|
-
|
529
|
+
|
530
|
+
# hack to prevent nokogiri form stripping namespace prefix on xml fragments
|
531
|
+
if @layout
|
532
|
+
html = %[<div id="trellis_remove" xmlns:trellis="http://trellisframework.org/schema/trellis_1_0_0.xsd">#{html}</div>]
|
533
|
+
end
|
534
|
+
|
535
|
+
@template = Nokogiri::XML(html)
|
536
|
+
|
385
537
|
find_components
|
386
538
|
end
|
387
539
|
|
388
|
-
def self.
|
540
|
+
def self.dom
|
389
541
|
# try to reload the template if it wasn't found on during inherited
|
390
542
|
# since it could have failed if the app was not mounted as root
|
391
543
|
unless @template
|
@@ -395,6 +547,10 @@ module Trellis
|
|
395
547
|
@template
|
396
548
|
end
|
397
549
|
|
550
|
+
def self.to_xml(options = {})
|
551
|
+
options[:no_declaration] ? dom.to_xml(:save_with => Node::SaveOptions::NO_DECLARATION) : dom.to_xml
|
552
|
+
end
|
553
|
+
|
398
554
|
def self.format
|
399
555
|
@format
|
400
556
|
end
|
@@ -421,22 +577,22 @@ module Trellis
|
|
421
577
|
@@subclasses
|
422
578
|
end
|
423
579
|
|
424
|
-
def initialize # TODO this is Ugly.... should
|
580
|
+
def initialize # TODO this is Ugly.... should not do it in initialize since it'll require super in child classes
|
425
581
|
self.class.stateful_components.each do |id_component|
|
426
582
|
id_component[1].enhance_page(self, id_component[0])
|
427
583
|
end
|
428
584
|
@logger = Application.logger
|
429
585
|
end
|
430
586
|
|
587
|
+
def redirect(path, status=nil)
|
588
|
+
Redirect.new(path, status)
|
589
|
+
end
|
590
|
+
|
431
591
|
def process_event(event, value, source, session)
|
432
592
|
method = source ? "on_#{event}_from_#{source}" : "on_#{event}"
|
433
593
|
|
434
|
-
# execute the method passing the value if necessary
|
435
|
-
|
436
|
-
method_result = send method.to_sym
|
437
|
-
else
|
438
|
-
method_result = send method.to_sym, Rack::Utils.unescape(value)
|
439
|
-
end
|
594
|
+
# execute the method passing the value if necessary
|
595
|
+
method_result = value ? send(method.to_sym, Rack::Utils.unescape(value)) : send(method.to_sym)
|
440
596
|
|
441
597
|
# determine navigation flow based on the return value of the method call
|
442
598
|
if method_result
|
@@ -474,6 +630,10 @@ module Trellis
|
|
474
630
|
result
|
475
631
|
end
|
476
632
|
|
633
|
+
def render_partial(name, locals={})
|
634
|
+
Renderer.new(self).render_partial(name, locals)
|
635
|
+
end
|
636
|
+
|
477
637
|
# inject an instance of each of the injected pages classes as instance variables
|
478
638
|
# of the current page
|
479
639
|
def inject_dependent_pages
|
@@ -498,7 +658,7 @@ module Trellis
|
|
498
658
|
end
|
499
659
|
|
500
660
|
template do
|
501
|
-
|
661
|
+
thtml {
|
502
662
|
head { title "Stand-in Page" }
|
503
663
|
body { h1 { text %[Stand-in Page for <trellis:value name="page_name"/>] }}
|
504
664
|
}
|
@@ -550,9 +710,8 @@ module Trellis
|
|
550
710
|
def self.find_components
|
551
711
|
@components.clear
|
552
712
|
classes_processed = []
|
553
|
-
doc = REXML::Document.new(@template.to_html)
|
554
713
|
# look for component declarations in the template
|
555
|
-
|
714
|
+
@template.xpath("//trellis:*", 'trellis' => "http://trellisframework.org/schema/trellis_1_0_0.xsd").each do |element|
|
556
715
|
# retrieve the component class
|
557
716
|
component = Component.get_component(element.name.to_sym)
|
558
717
|
# for components that are contained in other components
|
@@ -561,13 +720,13 @@ module Trellis
|
|
561
720
|
parent = nil
|
562
721
|
# loop over all the container types until we find the matching parent
|
563
722
|
component.containers.each do |container|
|
564
|
-
parent =
|
723
|
+
parent = element.xpath("ancestor::trellis:#{container}", 'trellis' => "http://trellisframework.org/schema/trellis_1_0_0.xsd").first
|
565
724
|
break if parent
|
566
725
|
end
|
567
|
-
element
|
726
|
+
element['parent_tid'] = parent['tid'] if parent
|
568
727
|
end
|
569
728
|
|
570
|
-
tid = element
|
729
|
+
tid = element['tid']
|
571
730
|
unless component
|
572
731
|
Application.logger.info "could not find #{element.name} in component hash"
|
573
732
|
else
|
@@ -638,11 +797,14 @@ module Trellis
|
|
638
797
|
class Renderer
|
639
798
|
include Radius
|
640
799
|
|
800
|
+
SKIP_METHODS = ['before_load', 'after_load', 'before_render', 'after_render', 'get']
|
801
|
+
INCLUDE_METHODS = ['render_partial']
|
802
|
+
|
641
803
|
def initialize(page)
|
642
804
|
@page = page
|
643
805
|
@context = Context.new
|
644
806
|
# context for erubis templates
|
645
|
-
@eruby_context =
|
807
|
+
@eruby_context = Erubis::Context.new #if @page.class.format == :eruby
|
646
808
|
|
647
809
|
# add all instance variables in the page as values accesible from the tags
|
648
810
|
page.instance_variables.each do |var|
|
@@ -650,19 +812,50 @@ module Trellis
|
|
650
812
|
unless value.kind_of?(Trellis::Page)
|
651
813
|
sym = "#{var}=".split('@').last.to_sym
|
652
814
|
@context.globals.send(sym, value)
|
653
|
-
@eruby_context["#{var}".split('@').last] = value if @eruby_context
|
815
|
+
@eruby_context["#{var}".split('@').last] = value #if @eruby_context
|
654
816
|
end
|
655
817
|
end
|
656
818
|
|
657
819
|
# add other useful values to the tag context
|
658
820
|
@context.globals.send(:page_name=, page.class.to_s)
|
659
|
-
@eruby_context[:page_name] = page.class.to_s if @eruby_context
|
821
|
+
@eruby_context[:page_name] = page.class.to_s #if @eruby_context
|
660
822
|
|
661
|
-
#
|
823
|
+
# add public page methods to the context
|
824
|
+
page.public_methods(false).each do |method_name|
|
825
|
+
# skip event handlers and the 'get' method
|
826
|
+
unless method_name.starts_with?('on_') || SKIP_METHODS.include?(method_name)
|
827
|
+
@eruby_context.meta_def(method_name) do |*args|
|
828
|
+
page.send(method_name.to_sym, *args)
|
829
|
+
end #if @eruby_context
|
830
|
+
@context.globals.meta_def(method_name) do |*args|
|
831
|
+
page.send(method_name.to_sym, *args)
|
832
|
+
end
|
833
|
+
end
|
834
|
+
end
|
835
|
+
|
836
|
+
# add page helper methods to the context
|
837
|
+
INCLUDE_METHODS.each do |method_name|
|
838
|
+
@eruby_context.meta_def(method_name) do |*args|
|
839
|
+
page.send(method_name.to_sym, *args)
|
840
|
+
end #if @eruby_context
|
841
|
+
@context.globals.meta_def(method_name) do |*args|
|
842
|
+
page.send(method_name.to_sym, *args)
|
843
|
+
end
|
844
|
+
end
|
845
|
+
|
846
|
+
# add public application methods to the context
|
847
|
+
page.application.public_methods(false).each do |method_name|
|
848
|
+
@eruby_context.meta_def(method_name) do |*args|
|
849
|
+
page.application.send(method_name.to_sym, *args)
|
850
|
+
end #if @eruby_context
|
851
|
+
@context.globals.meta_def(method_name) do |*args|
|
852
|
+
page.application.send(method_name.to_sym, *args)
|
853
|
+
end
|
854
|
+
end
|
662
855
|
|
663
856
|
# add the page to the context too
|
664
857
|
@context.globals.page = page
|
665
|
-
@eruby_context[:page] = page if @eruby_context
|
858
|
+
@eruby_context[:page] = page #if @eruby_context
|
666
859
|
|
667
860
|
# register the components contained in the page with the renderer's context
|
668
861
|
page.class.components.each do |component|
|
@@ -673,11 +866,54 @@ module Trellis
|
|
673
866
|
end
|
674
867
|
|
675
868
|
def render
|
676
|
-
|
677
|
-
|
869
|
+
preprocessed = ""
|
870
|
+
layout_id = @page.class.layout
|
871
|
+
template = layout_id ? @page.class.to_xml(:no_declaration => true) : @page.class.to_xml
|
872
|
+
|
873
|
+
if layout_id
|
874
|
+
# page has a layout
|
875
|
+
# retrieve the layout from the application
|
876
|
+
layout = Application.layouts[layout_id]
|
877
|
+
# render the page template to a variable
|
878
|
+
if @page.class.format == :eruby
|
879
|
+
body = Erubis::PI::Eruby.new(template, :trim => false).evaluate(@eruby_context)
|
880
|
+
@eruby_context[:body] = body
|
881
|
+
else
|
882
|
+
@eruby_context[:body] = template
|
883
|
+
end
|
884
|
+
|
885
|
+
# render the layout around the page template
|
886
|
+
preprocessed = Erubis::PI::Eruby.new(layout.to_xml, :trim => false).evaluate(@eruby_context)
|
887
|
+
|
888
|
+
# clean up nokogiri namespace hack, see Page#template
|
889
|
+
doc = Nokogiri::XML(preprocessed)
|
890
|
+
to_be_removed = doc.at_css(%[div[id="trellis_remove"]])
|
891
|
+
parent = to_be_removed.parent
|
892
|
+
to_be_removed.children.each { |child| child.parent = parent }
|
893
|
+
to_be_removed.remove
|
894
|
+
preprocessed = doc.to_xml
|
678
895
|
else
|
679
|
-
|
680
|
-
@
|
896
|
+
# page has no layout
|
897
|
+
if @page.class.format == :eruby
|
898
|
+
preprocessed = Erubis::PI::Eruby.new(template, :trim => false).evaluate(@eruby_context)
|
899
|
+
else
|
900
|
+
preprocessed = template
|
901
|
+
end
|
902
|
+
end
|
903
|
+
# radius parsing
|
904
|
+
@parser.parse(preprocessed)
|
905
|
+
end
|
906
|
+
|
907
|
+
def render_partial(name, locals={})
|
908
|
+
partial = Application.partials[name]
|
909
|
+
if partial
|
910
|
+
if partial.format == :eruby
|
911
|
+
locals.each_pair { |n,v| @eruby_context[n] = v }
|
912
|
+
preprocessed = Erubis::PI::Eruby.new(partial.to_xml, :trim => false).evaluate(@eruby_context)
|
913
|
+
@parser.parse(preprocessed)
|
914
|
+
else
|
915
|
+
@parser.parse(partial.to_xml)
|
916
|
+
end
|
681
917
|
end
|
682
918
|
end
|
683
919
|
|
@@ -762,7 +998,7 @@ module Trellis
|
|
762
998
|
href = href.replace_ant_style_properties(attributes) if attributes
|
763
999
|
builder = Builder::XmlMarkup.new
|
764
1000
|
link = builder.link(:rel => "stylesheet", :type => "text/css", :href => href)
|
765
|
-
page.
|
1001
|
+
page.dom.at_css("html/head").children.last.after("\n#{link}")
|
766
1002
|
end
|
767
1003
|
end
|
768
1004
|
|
@@ -771,7 +1007,7 @@ module Trellis
|
|
771
1007
|
src = src.replace_ant_style_properties(attributes) if attributes
|
772
1008
|
builder = Builder::XmlMarkup.new
|
773
1009
|
script = builder.script('', :type => "text/javascript", :src => src)
|
774
|
-
page.
|
1010
|
+
page.dom.at_css("html/head").children.last.after("\n#{script}")
|
775
1011
|
end
|
776
1012
|
end
|
777
1013
|
|
@@ -782,7 +1018,7 @@ module Trellis
|
|
782
1018
|
style = builder.style(:type => "text/css") do |builder|
|
783
1019
|
builder << body
|
784
1020
|
end
|
785
|
-
page.
|
1021
|
+
page.dom.at_css("html/head").children.last.after("\n#{style}")
|
786
1022
|
end
|
787
1023
|
end
|
788
1024
|
|
@@ -793,7 +1029,7 @@ module Trellis
|
|
793
1029
|
script = builder.script(:type => "text/javascript") do |builder|
|
794
1030
|
builder << body
|
795
1031
|
end
|
796
|
-
page.
|
1032
|
+
page.dom.at_css("html/body").children.last.after("\n#{script}")
|
797
1033
|
end
|
798
1034
|
end
|
799
1035
|
|
@@ -804,7 +1040,7 @@ module Trellis
|
|
804
1040
|
style = builder.style(:type => "text/css") do |builder|
|
805
1041
|
builder << body
|
806
1042
|
end
|
807
|
-
page.
|
1043
|
+
page.dom.at_css("html/head").children.last.after("\n#{style}")
|
808
1044
|
end
|
809
1045
|
end
|
810
1046
|
|
@@ -815,13 +1051,13 @@ module Trellis
|
|
815
1051
|
script = builder.script(:type => "text/javascript") do |builder|
|
816
1052
|
builder << body
|
817
1053
|
end
|
818
|
-
page.
|
1054
|
+
page.dom.at_css("html/body").children.last.after("\n#{script}")
|
819
1055
|
end
|
820
1056
|
end
|
821
1057
|
|
822
1058
|
def self.add_document_modifications_to_page(page)
|
823
1059
|
document_modifications.each do |block|
|
824
|
-
page.
|
1060
|
+
page.dom.instance_eval(&block)
|
825
1061
|
end
|
826
1062
|
end
|
827
1063
|
|
@@ -926,4 +1162,4 @@ module Trellis
|
|
926
1162
|
require 'trellis/component_library/core_components'
|
927
1163
|
require 'trellis/component_library/grid'
|
928
1164
|
require 'trellis/component_library/object_editor'
|
929
|
-
end
|
1165
|
+
end
|