erector 0.7.0 → 0.7.1

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,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