trellis 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 xmlns="http://www.w3.org/1999/xhtml">
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 xmlns="http://www.w3.org/1999/xhtml">
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" />
@@ -64,7 +64,7 @@ module Flickr
64
64
 
65
65
  class Home < Page
66
66
  template do
67
- xhtml_strict {
67
+ thtml {
68
68
  head {
69
69
  title "Trellis Flickr Component Test"
70
70
  }
@@ -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 xmlns="http://www.w3.org/1999/xhtml">
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 xmlns="http://www.w3.org/1999/xhtml" xmlns:trellis="http://labs.integralis.org/schema/trellis_1_0_0.xsd">
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 xmlns="http://www.w3.org/1999/xhtml">
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 xmlns="http://www.w3.org/1999/xhtml">
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 xmlns="http://www.w3.org/1999/xhtml">
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 xmlns="http://www.w3.org/1999/xhtml">
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>
@@ -32,7 +32,7 @@ module Routing
32
32
  end
33
33
 
34
34
  template do
35
- html {
35
+ thtml {
36
36
  body {
37
37
  h2 {
38
38
  text %[Date <trellis:value name="page.parse_date"/>]
@@ -17,7 +17,7 @@ module Simplest
17
17
  end
18
18
 
19
19
  template do # using Markaby
20
- xhtml_strict {
20
+ thtml {
21
21
  head { title "Simplest Trellis Application" }
22
22
  body {
23
23
  h1 "Simplest Trellis Application"
@@ -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" xmlns="http://www.w3.org/1999/xhtml" xml: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
- target_page = tag.globals.page.class.name
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("name" => form_name, "action" => href, "method" => method) do |form|
255
+ builder.form(attrs) do |form|
240
256
  form << tag.expand
241
257
  end
242
258
  end
@@ -30,8 +30,7 @@ require 'rubygems'
30
30
  require 'rack'
31
31
  require 'radius'
32
32
  require 'builder'
33
- require 'hpricot'
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
- # Rack call interface.
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
- if (request.post? || route.event) && result.kind_of?(Trellis::Page)
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
- # for render requests simply render the page
170
- response.body = result.kind_of?(Trellis::Page) ? result.render : result
171
- response.status = 200
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
- destination = page.kind_of?(Trellis::Page) ? (page.path || page.class.class_to_sym) : page
325
- url_root = page.kind_of?(Trellis::Page) && page.class.url_root ? "/#{page.class.url_root}" : '/' unless url_root
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
- html = "<html><body>#{BlueCloth.new(body).to_html}</body></html>"
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
- @template = Hpricot.XML(html)
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.parsed_template
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 no do it in initialize since it'll require super in child classes
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
- unless value
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
- xhtml_strict {
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
- doc.elements.each('//trellis:*') do |element|
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 = REXML::XPath.first(element, "ancestor::trellis:#{container}")
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.attributes['parent_tid'] = parent.attributes['tid'] if parent
726
+ element['parent_tid'] = parent['tid'] if parent
568
727
  end
569
728
 
570
- tid = element.attributes['tid']
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 = {} if @page.class.format == :eruby
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
- #TODO add public page methods to the context
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
- unless @page.class.format == :eruby
677
- @parser.parse(@page.class.parsed_template.to_html)
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
- preprocessed = Erubis::PI::Eruby.new(@page.class.parsed_template.to_html, :trim => false).evaluate(@eruby_context)
680
- @parser.parse(preprocessed)
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.parsed_template.at("html/head").containers.last.after("\n#{link}")
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.parsed_template.at("html/head").containers.last.after("\n#{script}")
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.parsed_template.at("html/head").containers.last.after("\n#{style}")
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.parsed_template.at("html/body").containers.last.after("\n#{script}")
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.parsed_template.at("html/head").containers.last.after("\n#{style}")
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.parsed_template.at("html/body").containers.last.after("\n#{script}")
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.parsed_template.instance_eval(&block)
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