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.
@@ -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