erector 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1,4 @@
1
1
  require "#{File.dirname(__FILE__)}/widgets/table"
2
-
2
+ require "#{File.dirname(__FILE__)}/widgets/field_table"
3
+ require "#{File.dirname(__FILE__)}/widgets/page"
4
+ require "#{File.dirname(__FILE__)}/widgets/environment_badge"
@@ -0,0 +1,29 @@
1
+ module Erector
2
+ module Widgets #:nodoc:
3
+
4
+ # Displays a colored badge in the upper-left corner
5
+ # signifying the environment the app is running in.
6
+ # Inspired by Assaf Arkin
7
+ # <http://blog.labnotes.org/2009/10/08/using-a-badge-to-distinguish-development-and-production-environments/>
8
+ # Erectorized by Alex Chaffee
9
+ class EnvironmentBadge < Erector::Widget
10
+ def content
11
+ style <<-STYLE
12
+ #environment_badge { position: fixed; left: 1em; font-weight: bold; padding: .2em 0.9em; text-transform: uppercase; display: none }
13
+ #environment_badge.staging { color: #000; background: #ffff00; border: 2px solid #cccc20; }
14
+ #environment_badge.development { color: #fff; background: #ff0000; border: 2px solid #cc2020; }
15
+ #environment_badge.staging, #environment_badge.development { border-top: none; display: block; opacity: 0.6 }
16
+ STYLE
17
+ unless environment =~ /production/
18
+ p environment, :class => environment, :id => "environment_badge"
19
+ end
20
+ end
21
+
22
+ def environment
23
+ RAILS_ENV
24
+ rescue NameError
25
+ ENV['RAILS_ENV'] || ENV['RACK_ENV']
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,110 @@
1
+ # A simple HTML table with three columns: label, contents, and (optionally) note.
2
+ # Each row is called a field.
3
+ # The last row can optionally contain "buttons" (actually any widget will do)
4
+ # that are all shown in the 2nd column.
5
+ # It's all surrounded with a fieldset element for a title.
6
+ #
7
+ # In ASCII art, it looks something like this:
8
+ #
9
+ # /-Meals----------------------------------------\
10
+ # | Breakfast | eggs | |
11
+ # | Lunch | sandwich | |
12
+ # | Dinner | lo mein | with shrimp! |
13
+ # | | [Save] [Cancel] | |
14
+ # \----------------------------------------------/
15
+ #
16
+ # There are two ways to create a FieldTable.
17
+ # 1. Pass a block in to the constructor.
18
+ # 2. Make a subclass.
19
+ # In both cases you'll want to call the "field" and "button" methods on the table.
20
+ # This sets up the contents which will be rendered later during FieldTable#content.
21
+ # If you make a subclass (#2) you can do this either in the constructor or in
22
+ # the content method *before* you call super.
23
+ #
24
+ # The FieldTable is surrounded by a fieldset element whose legend is the "title" instance variable.
25
+ # Inside this fieldset is a table element.
26
+ # Each field (row of the table) has a label (th), a content cell (td), and an optional note (td).
27
+ #
28
+ # If you call "button" you can pass in a block that'll get rendered inside the 2nd column of the last row. The idea here is that you might want to make an HTML form that has a bunch of buttons at the bottom (Save, Cancel, Clear) and these all go in the same cell, with no label for the row.
29
+ #
30
+ # TODO: error messages?
31
+ # @author Alex Chaffee
32
+ class FieldTable < Erector::Widget
33
+
34
+ include Erector::Inline
35
+
36
+ class Field < Erector::Widget
37
+ needs :label, :note => nil
38
+
39
+ def content
40
+ tr :class => "field_table_field" do
41
+ th do
42
+ text @label
43
+ text ":" unless @label.nil?
44
+ end
45
+ td do
46
+ super # calls the block
47
+ end
48
+ if @note
49
+ td do
50
+ text @note
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ # Define a field, containing a label, optional note, and block for the contents
58
+ #
59
+ # TODO: allow passing in a widget instead of a block
60
+ def field(label = nil, note = nil, &contents)
61
+ @fields << Field.new(:label => label, :note => note, &contents)
62
+ end
63
+
64
+ # If you call "button" you can pass in a block that'll get rendered inside the 2nd column of the
65
+ # last row. The idea here is that you might want to make an HTML form that has a bunch of buttons at
66
+ # the bottom (Save, Cancel, Clear) and these all go in the same cell, with no label for the row.
67
+ #
68
+ # TODO: allow passing in a widget instead of a block
69
+ def button(&button_proc)
70
+ @buttons << button_proc
71
+ end
72
+
73
+ needs :title
74
+
75
+ # Pass in a block and it'll get called with a pointer to this table, so you can call
76
+ # 'field' and 'button' to configure it
77
+ def initialize(*args)
78
+ super
79
+ @fields = []
80
+ @buttons = []
81
+ yield self if block_given? # invoke the configuration block
82
+ end
83
+
84
+ def content
85
+ fieldset :class => "field_table" do
86
+ legend @title
87
+ table :width => '100%' do
88
+ @fields.each do |f|
89
+ widget f
90
+ end
91
+ unless @buttons.empty?
92
+ tr :class => "field_table_buttons" do
93
+ td :colspan => 2, :align => "right" do
94
+ table :class => 'layout' do
95
+ tr do
96
+ @buttons.each do |button|
97
+ td :class => "field_table_button" do
98
+ button.call
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ end
@@ -0,0 +1,158 @@
1
+ # Erector Page base class.
2
+ #
3
+ # Allows for accumulation of script and style tags (see example below) with either
4
+ # external or inline content. External references are 'uniq'd, so it's a good idea to declare
5
+ # a js script in all widgets that use it, so you don't accidentally lose the script if you remove
6
+ # the one widget that happened to declare it.
7
+ #
8
+ # At minimum, child classes must override #body_content. You can also get a "quick and dirty"
9
+ # page by passing a block to Page.new but that doesn't really buy you much.
10
+ #
11
+ # The script and style declarations are accumulated at class load time, as 'externals'.
12
+ # This technique allows all widgets to add their own requirements to the page header
13
+ # without extra logic for declaring which pages include which nested widgets.
14
+ # Unfortunately, this means that every page in the application will share the same headers,
15
+ # which may lead to conflicts.
16
+ #
17
+ # If you want something to show up in the headers for just one page type (subclass),
18
+ # then override #head_content, call super, and then emit it yourself.
19
+ #
20
+ # Author:: Alex Chaffee, alex@stinky.com
21
+ #
22
+ # = Example Usage:
23
+ #
24
+ # class MyPage < Page
25
+ # external :js, "lib/jquery.js"
26
+ # external :script, "$(document).ready(function(){...});"
27
+ # external :css, "stuff.css"
28
+ # external :style, "li.foo { color: red; }"
29
+ #
30
+ # def page_title
31
+ # "my app"
32
+ # end
33
+ #
34
+ # def body_content
35
+ # h1 "My App"
36
+ # p "welcome to my app"
37
+ # widget WidgetWithExternalStyle
38
+ # end
39
+ # end
40
+ #
41
+ # class WidgetWithExternalStyle < Erector::Widget
42
+ # external :style, "div.custom { border: 2px solid green; }"
43
+ #
44
+ # def content
45
+ # div :class => "custom" do
46
+ # text "green is good"
47
+ # end
48
+ # end
49
+ # end
50
+ #
51
+ # = Thoughts:
52
+ # * It may be desirable to unify #js and #script, and #css and #style, and have the routine be
53
+ # smart enough to analyze its parameter to decide whether to make it a file or a script.
54
+ #
55
+ class Erector::Widgets::Page < Erector::Widget
56
+
57
+ needs :basic_styles => true
58
+
59
+ # Emit the Transitional doctype.
60
+ # TODO: allow selection from among different standard doctypes
61
+ def doctype
62
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
63
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
64
+ end
65
+
66
+ def content
67
+ rawtext doctype
68
+ # todo: allow customization of xmlns and xml:lang
69
+ html :xmlns => 'http://www.w3.org/1999/xhtml', 'xml:lang' => 'en', :lang => 'en' do
70
+ head do
71
+ head_content
72
+ end
73
+ body :class => body_class do
74
+ body_content
75
+ end
76
+ end
77
+ end
78
+
79
+ # override me to provide a page title (default = name of the Page subclass)
80
+ def page_title
81
+ self.class.name
82
+ end
83
+
84
+ # override me to add a css class to the body
85
+ def body_class
86
+ end
87
+
88
+ # override me (or instantiate Page with a block)
89
+ def body_content
90
+ instance_eval(&@block) if @block
91
+ end
92
+
93
+ # emit the contents of the head element. Override and call super if you want to put more stuff in there.
94
+ def head_content
95
+ meta 'http-equiv' => 'content-type', :content => 'text/html;charset=UTF-8'
96
+ title page_title
97
+
98
+ basic_styles if @basic_styles
99
+ included_stylesheets
100
+ inline_styles
101
+
102
+ included_scripts
103
+ inline_scripts
104
+ end
105
+
106
+ def included_scripts
107
+ self.class.externals(:js).each do |file|
108
+ script :type => "text/javascript", :src => file
109
+ end
110
+ end
111
+
112
+ def included_stylesheets
113
+ self.class.externals(:css).each do |file|
114
+ # todo: allow different media
115
+ link :rel => "stylesheet", :href => file, :type => "text/css", :media => "all"
116
+ end
117
+ end
118
+
119
+ # Emit some *very* basic styles, hopefully not too controversial. Suppress
120
+ # them by setting :basic_styles => false when you construct your Page, or just
121
+ # override the basic_styles method in your subclass and make it do nothing.
122
+ # You can also redefine them since they're defined above any other styles in the HEAD.
123
+ #
124
+ # Class "right" floats right, class "left" floats left, and class "clear" clears
125
+ # any floats on both sides while being as small as possible to minimize impact
126
+ # on your layout. And images have no border.
127
+ def basic_styles
128
+ style <<-STYLE
129
+ img {border: none}
130
+ .right {float: right;}
131
+ .left {float: left;}
132
+ .clear {background: none;border: 0;clear: both;display: block;float: none;font-size: 0;margin: 0;padding: 0;position: static;overflow: hidden;visibility: hidden;width: 0;height: 0;}
133
+ STYLE
134
+ end
135
+
136
+ def inline_styles
137
+ style :type => "text/css", 'xml:space' => 'preserve' do
138
+ rawtext "\n"
139
+ self.class.externals(:style).each do |txt|
140
+ rawtext "\n"
141
+ rawtext txt
142
+ end
143
+ end
144
+ end
145
+
146
+ def inline_scripts
147
+ javascript do
148
+ self.class.externals(:script).each do |txt|
149
+ rawtext "\n"
150
+ rawtext txt
151
+ end
152
+ self.class.externals(:jquery).each do |txt|
153
+ jquery_ready txt
154
+ end
155
+ end
156
+ end
157
+
158
+ end
@@ -0,0 +1,52 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
+ require 'benchmark'
3
+
4
+ describe "external declarations" do
5
+ class HotSauce < Erector::Widget
6
+ external :css, "/css/tapatio.css"
7
+ external :css, "/css/salsa_picante.css"
8
+ external :js, "/lib/jquery.js"
9
+ external :js, "/lib/picante.js"
10
+ end
11
+
12
+ class SourCream < Erector::Widget
13
+ external :css, "/css/sourcream.css"
14
+ external :js, "/lib/jquery.js"
15
+ external :js, "/lib/dairy.js"
16
+ end
17
+
18
+ it "can be fetched via the type" do
19
+ Erector::Widget.externals(:css).should == [
20
+ "/css/tapatio.css",
21
+ "/css/salsa_picante.css",
22
+ "/css/sourcream.css",
23
+ ]
24
+ end
25
+
26
+ it "can be filtered via the class" do
27
+ Erector::Widget.externals(:css, HotSauce).should == [
28
+ "/css/tapatio.css",
29
+ "/css/salsa_picante.css",
30
+ ]
31
+ Erector::Widget.externals(:css, SourCream).should == [
32
+ "/css/sourcream.css",
33
+ ]
34
+ end
35
+
36
+ it "removes duplicates" do
37
+ Erector::Widget.externals(:js).should == [
38
+ "/lib/jquery.js",
39
+ "/lib/picante.js",
40
+ "/lib/dairy.js",
41
+ ]
42
+ end
43
+
44
+ it "works with strings or symbols" do
45
+ Erector::Widget.externals("js").should == [
46
+ "/lib/jquery.js",
47
+ "/lib/picante.js",
48
+ "/lib/dairy.js",
49
+ ]
50
+ end
51
+
52
+ end
@@ -212,7 +212,7 @@ module WidgetSpec
212
212
  :nil_attribute => nil
213
213
  )
214
214
  end.to_s
215
- doc = Hpricot(html)
215
+ doc = Nokogiri::HTML(html)
216
216
  div = doc.at('div')
217
217
  div[:class].should == "foo bar"
218
218
  div[:style].should == "display: none; color: white; float: left;"
@@ -430,6 +430,40 @@ module WidgetSpec
430
430
  end
431
431
  end
432
432
  end
433
+
434
+ def capturing_output
435
+ output = StringIO.new
436
+ $stdout = output
437
+ yield
438
+ output.string
439
+ ensure
440
+ $stdout = STDOUT
441
+ end
442
+
443
+ describe "#comment" do
444
+ it "emits an HTML comment" do
445
+ Erector.inline do
446
+ comment "foo"
447
+ end.to_s.should == "<!--foo-->"
448
+ end
449
+
450
+ # see http://www.w3.org/TR/html4/intro/sgmltut.html#h-3.2.4
451
+ it "does not HTML-escape character references" do
452
+ Erector.inline do
453
+ comment "&nbsp;"
454
+ end.to_s.should == "<!--&nbsp;-->"
455
+ end
456
+
457
+ # see http://www.w3.org/TR/html4/intro/sgmltut.html#h-3.2.4
458
+ # "Authors should avoid putting two or more adjacent hyphens inside comments."
459
+ it "warns if there's two hyphens in a row" do
460
+ capturing_output do
461
+ Erector.inline do
462
+ comment "he was -- awesome!"
463
+ end.to_s.should == "<!--he was -- awesome!-->"
464
+ end.should == "Warning: Authors should avoid putting two or more adjacent hyphens inside comments.\n"
465
+ end
466
+ end
433
467
 
434
468
  describe "#nbsp" do
435
469
  it "turns consecutive spaces into consecutive non-breaking spaces" do
@@ -594,8 +628,8 @@ module WidgetSpec
594
628
  html = Erector.inline do
595
629
  javascript(:src => "/my/js/file.js")
596
630
  end.to_s
597
- doc = Hpricot(html)
598
- doc.at('/')[:src].should == "/my/js/file.js"
631
+ doc = Nokogiri::HTML(html)
632
+ doc.at("script")[:src].should == "/my/js/file.js"
599
633
  end
600
634
  end
601
635
 
@@ -604,7 +638,7 @@ module WidgetSpec
604
638
  html = Erector.inline do
605
639
  javascript('alert("&<>\'hello");', :src => "/my/js/file.js")
606
640
  end.to_s
607
- doc = Hpricot(html)
641
+ doc = Nokogiri::HTML(html)
608
642
  script_tag = doc.at('script')
609
643
  script_tag[:src].should == "/my/js/file.js"
610
644
  script_tag.inner_html.should include('alert("&<>\'hello");')
@@ -709,15 +743,25 @@ module WidgetSpec
709
743
 
710
744
  end
711
745
 
712
- describe "assigning local variables" do
746
+ describe "assigning instance variables" do
713
747
  it "attempting to overwrite a reserved instance variable raises error" do
714
748
  lambda {
715
749
  Erector::Widget.new(:output => "foo")
716
750
  }.should raise_error(ArgumentError)
717
751
  end
752
+
753
+ it "handles instance variable names with and without '@' in the beginning" do
754
+ html = Erector.inline(:foo => "bar", '@baz' => 'quux') do
755
+ div do
756
+ p @foo
757
+ p @baz
758
+ end
759
+ end.to_s
760
+ doc = Nokogiri::HTML(html)
761
+ doc.css("p").map {|p| p.inner_html}.should == ["bar", "quux"]
762
+ end
718
763
  end
719
764
 
720
-
721
765
  context "when declaring parameters with the 'needs' macro" do
722
766
  it "doesn't complain if there aren't any needs declared" do
723
767
  class Thing1 < Erector::Widget