active_component 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ class Block < ActiveComponent::Base
4
+
5
+ attr_accessor :tag_type
6
+
7
+ def initialize(*args, &content_block)
8
+ init_component(args, [:content, :title, :tag_type, :attributes], &content_block)
9
+
10
+ # Defaults
11
+ @tag_type ||= :div
12
+ end
13
+
14
+ def to_html
15
+ wrap_contents(@tag_type, content, nil, @attributes)
16
+ end
17
+
18
+ def_html_sub_components ActiveComponent::BLOCK_ELEMENTS, self
19
+ end
20
+
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ class EmptyTag < ActiveComponent::Base
4
+
5
+ attr_accessor :tag_type
6
+
7
+ # Content can be passed as a block
8
+ def initialize(*args)
9
+ init_component(args, [:title, :tag_type, :attributes])
10
+
11
+ # Defaults
12
+ @tag_type ||= :br
13
+ end
14
+
15
+ def to_html
16
+ if ActiveComponent::Config.component_options[:validate_html]
17
+ raise InvalidHtmlError, "Empty HTML elements must not have content." if content.present?
18
+ end
19
+
20
+ name, attrs = merge_name_and_attributes(@tag_type.to_s, @attributes)
21
+ attrs = Haml::Precompiler.build_attributes(@haml_buffer.html?, @haml_buffer.options[:attr_wrapper], attrs)
22
+ "<#{name}#{attrs} />"
23
+ end
24
+
25
+ def_html_sub_components ActiveComponent::EMPTY_ELEMENTS, self
26
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ class Heading < ActiveComponent::Base
4
+
5
+ attr_reader :level
6
+
7
+ def level=(level)
8
+ unless level.nil?
9
+ raise ArgumentError, "heading_level must be numeric (given #{level.inspect} in heading #{@content})" unless level.is_a? Numeric
10
+ puts "warning: heading_level should be an integer and between 1 and 6 (given #{level} in heading #{@content})" unless level.between?(1,6)
11
+ @level = [[level.to_i, 6].min, 1].max
12
+ end
13
+ end
14
+
15
+ def initialize(*args, &content_block)
16
+ init_component(args, [:content, :title, :level, :attributes], &content_block)
17
+ end
18
+
19
+ def to_html
20
+ @level ||= determine_level
21
+
22
+ wrap_contents("h" + @level.to_s, content, nil, @attributes)
23
+ end
24
+
25
+ # Determines the heading level by adopting the siblings' one
26
+ # or by determining the parent's one recursively
27
+ def determine_level
28
+ return 1 if is_root?
29
+ return level if level.present?
30
+
31
+ siblings_level or (
32
+ if Heading.has_parent_heading?(self)
33
+ Heading.parent_heading(self).determine_level + 1
34
+ else
35
+ 1
36
+ end
37
+ )
38
+ end
39
+
40
+ # Collects the level of sibling headings
41
+ def siblings_level
42
+ siblings.collect {|sib| sib.level if sib.is_a?(Heading)}.compact.min
43
+ end
44
+
45
+ # Retrieves the next Heading of the node hierarchy above a given node
46
+ def Heading.parent_heading(node)
47
+ raise ArgumentException, "Node has no heading parent." unless Heading.has_parent_heading?(node)
48
+ node.parent.siblings.find_a(Heading) or Heading.parent_heading(node.parent)
49
+ end
50
+
51
+ # Checks whether a Heading exists in the node hierarchy above a given node
52
+ def Heading.has_parent_heading?(node)
53
+ !node.is_root? && (
54
+ node.parent.siblings.includes_a?(Heading) ||
55
+ Heading.has_parent_heading?(node.parent)
56
+ )
57
+ end
58
+
59
+ def_html_sub_components ActiveComponent::HEADING_ELEMENTS, self
60
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ class InlineTag < ActiveComponent::Base
4
+
5
+ attr_accessor :tag_type
6
+
7
+ # Content can be passed as a block
8
+ def initialize(*args, &content_block)
9
+ init_component(args, [:content, :title, :tag_type, :attributes], &content_block)
10
+
11
+ # Defaults
12
+ @tag_type ||= :span
13
+ end
14
+
15
+ def to_html
16
+ if ActiveComponent::Config.component_options[:validate_html]
17
+ raise InvalidHtmlError, "Inline tags must not have blocks as inner content." if content.includes_a? Block
18
+ end
19
+
20
+ wrap_contents(@tag_type, content, nil, @attributes)
21
+ end
22
+
23
+ def_html_sub_components ActiveComponent::PHRASING_ELEMENTS, self
24
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ class Section < ActiveComponent::Base
4
+
5
+ attr_accessor :tag_type, :heading, :heading_level, :heading_attrs
6
+
7
+ def initialize(*args, &content_block)
8
+ init_component(args, [:content, :title, :tag_type, :heading, :heading_level, :heading_attrs, :attributes], &content_block)
9
+
10
+ # Defaults
11
+ @tag_type ||= :section
12
+
13
+ # Validations
14
+ raise ArgumentError, "attributes must be a hash (given #{@attributes.inspect} in section #{@title})" unless @attributes.is_a? Hash
15
+ # TODO: Heading rank
16
+ end
17
+
18
+ def to_html
19
+ if @heading.present? && !@heading.is_a?(ActiveComponent)
20
+ @heading = Heading.new @heading, @heading_level, @heading_attrs
21
+ children.nil? ? (self << @heading) : self.prepend(@heading)
22
+ end
23
+
24
+ # TODO: Is this clean? Is there a better way that hides buffer operations?
25
+ # wrap_contents(@tag_type, :attributes => @attributes, :content => [@heading, content]
26
+
27
+ print_buffer do
28
+ tag_to_buffer @tag_type, @attributes do
29
+ write_to_buffer print_object(@heading)
30
+ content.transmogrify do |content|
31
+ write_to_buffer print_object(content)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def_html_sub_components ActiveComponent::SECTION_ELEMENTS, self
38
+ end
39
+
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+
3
+ class Table < ActiveComponent::Base
4
+
5
+ attr_accessor :cols, :headers, :row_attrs, :header_attrs, :field_attrs
6
+
7
+ def initialize(*args)
8
+ init_component(args, [:content, :title, :cols, :headers, :attributes, :row_attrs, :header_attrs, :field_attrs])
9
+
10
+ # Defaults
11
+ if @title.nil?
12
+ @title = content.first.class.to_s.hyphenize.pluralize
13
+ @attributes[:class].to_a.unshift @title
14
+ end
15
+ if @cols.nil? && content.first.respond_to?(:attributes)
16
+ @cols = content.first.attributes.keys
17
+ @headers ||= @cols.collect {|col| col.to_s.humanize}
18
+ end
19
+ @attributes[:cellspacing] ||= 0
20
+ @row_attrs ||= {}
21
+ @header_attrs ||= {}
22
+ @field_attrs ||= {}
23
+ end
24
+
25
+ def to_html
26
+ print_buffer do
27
+ tag_to_buffer :table, @attributes do
28
+ row_count = 0
29
+ unless @headers.blank?
30
+ tag_to_buffer :tr, get_row_attrs(row_count) do
31
+ @headers.each_with_index do |header, i|
32
+ tag_to_buffer :th, header, get_header_attrs(i)
33
+ end
34
+ row_count = 1
35
+ end
36
+ end
37
+ content.each_with_index do |row, i|
38
+ unless row.blank?
39
+ tag_to_buffer :tr, get_row_attrs(row_count + i) do
40
+ print_contents(:td, row, @cols, @field_attrs)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+ def get_attrs(attrs_collection, index = nil)
50
+ attrs = attrs_collection[index] || attrs_collection
51
+ attrs.is_a?(Hash) ? attrs : {}
52
+ end
53
+
54
+ def get_row_attrs(index = nil)
55
+ get_attrs(@row_attrs, index)
56
+ end
57
+
58
+ def get_header_attrs(index = nil)
59
+ get_attrs(@header_attrs, index)
60
+ end
61
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ module ActiveComponent
4
+ # The module for all global ActiveComponent configurations
5
+ module Config
6
+ extend self
7
+
8
+ @component_options = {}
9
+ # The options hash for Haml when used within Rails.
10
+ # See {file:HAML_REFERENCE.md#haml_options the Haml options documentation}.
11
+ #
12
+ # @return [{Symbol => Object}]
13
+ attr_accessor :component_options
14
+
15
+ def template_engine_options
16
+ Haml::Template.options
17
+ end
18
+
19
+ def template_engine_options=(options)
20
+ Haml::Template.options = options
21
+ end
22
+
23
+ template_engine_options[:format] ||= :html5
24
+
25
+ if Haml::Util.rails_env == "development"
26
+ component_options[:validate_html] ||= true
27
+ template_engine_options[:ugly] ||= false
28
+ else
29
+ component_options[:validate_html] ||= false
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,99 @@
1
+ # encoding: utf-8
2
+
3
+ class Object
4
+
5
+ # Transmogrify yields self to the given block by default
6
+ def transmogrify(*ignored_args)
7
+ yield self
8
+ end
9
+
10
+ # Wrapper for enumerable transmogrify
11
+ def transmogrify_with_index(&block)
12
+ transmogrify(:yield_index, &block)
13
+ end
14
+
15
+ alias :includes_a? :is_a?
16
+
17
+ end
18
+
19
+ class String
20
+
21
+ # Performs same transformation as underscore, but with hyphens
22
+ def hyphenize
23
+ gsub(/::/, '/').
24
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
25
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
26
+ tr("_", "-").
27
+ tr(" ", "-").
28
+ downcase
29
+ end
30
+
31
+ end
32
+
33
+ class Symbol
34
+
35
+ def to_class_constant
36
+ to_s.camelize.constantize
37
+ end
38
+
39
+ end
40
+
41
+ module Enumerable
42
+
43
+ # Transmogrify yields each element to the given block
44
+ def transmogrify(*options)
45
+ if options.include? :yield_index
46
+ each_with_index do |element, index|
47
+ yield element, index
48
+ end
49
+ else
50
+ each do |element|
51
+ yield element
52
+ end
53
+ end
54
+ end
55
+
56
+ # Determines if enumerable contains an object of the specified class
57
+ def includes_a?(klass)
58
+ each do |e|
59
+ return true if e.is_a? klass
60
+ end
61
+ false
62
+ end
63
+
64
+ # Returns the first object of the specified class contained in enumerable
65
+ def find_a(klass)
66
+ each do |e|
67
+ return e if e.is_a? klass
68
+ end
69
+ nil
70
+ end
71
+
72
+ end
73
+
74
+ # FIXME: There should be a better way to provide the module to ActionView.
75
+ class ActionView::Base
76
+ include ActiveComponent
77
+ end
78
+
79
+ if defined? ActiveSupport::CoreExtensions::Hash::ReverseMerge
80
+ # Rails 2
81
+ module ActiveSupport::CoreExtensions::Hash::ReverseMerge
82
+ alias :set_defaults :reverse_merge
83
+ alias :set_defaults! :reverse_merge!
84
+ end
85
+ else
86
+ # Rails 3
87
+ class Hash
88
+ alias :set_defaults :reverse_merge
89
+ alias :set_defaults! :reverse_merge!
90
+ end
91
+ end
92
+
93
+ module Haml::Helpers
94
+ alias :init_buffer :init_haml_helpers
95
+ alias :print_buffer :capture_haml
96
+ alias :tag_to_buffer :haml_tag
97
+ alias :write_to_buffer :haml_concat
98
+ alias :string_to_buffer :haml_concat
99
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ module ActiveComponent
4
+ class TemplateHandler
5
+ include ActionView::TemplateHandlers::Compilable
6
+
7
+ def compile(template)
8
+ # For Rails < 2.1.0, template is a string
9
+ # For Rails >= 2.1.0, template is a Template object
10
+ if template.respond_to? :source
11
+ # # For Rails >=3.0.0, there is a generic identifier
12
+ # options[:filename] = template.respond_to?(:identifier) ? template.identifier : template.filename
13
+ template.source
14
+ else
15
+ template
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,156 @@
1
+ # encoding: utf-8
2
+
3
+ require 'active_component_spec_helper'
4
+
5
+ describe ActiveComponent do
6
+
7
+ before(:each) { @comp = Block.new }
8
+ after(:each) { @comp = nil }
9
+
10
+ describe "print_contents" do
11
+ it "should print content and wrap it with a tag" do
12
+ content = "content"
13
+ @comp.print_contents(:span, content).should == "<span>content</span>\n"
14
+ end
15
+
16
+ it "should print multiple contents and wrap each item with a tag" do
17
+ content = [:a, "b", 3]
18
+ Factory.sequence(:content) {|n| content[n]}
19
+ @comp.print_contents(:span, content).should == "<span>a</span>\n<span>b</span>\n<span>3</span>\n"
20
+ end
21
+
22
+ it "should print content using a method and wrap the output with a tag" do
23
+ deep_thought = mock :content
24
+ deep_thought.should_receive(:question).once.and_return(42)
25
+ @comp.print_contents("div.the-answer", deep_thought, :question).should == "<div class='the-answer'>42</div>\n"
26
+ end
27
+
28
+ it "should print multiple contents using a method and wrap each one with a tag" do
29
+ names = %w(Aron Noel Trebor)
30
+ transformation = Proc.new {|x| x.downcase.reverse.humanize}
31
+ @comp.print_contents(:li, names, transformation).should == "<li>Nora</li>\n<li>Leon</li>\n<li>Robert</li>\n"
32
+ end
33
+
34
+ it "should print multiple contents using a set of methods and wrap each of the method outputs with a tag" do
35
+ things = []
36
+ things << Factory.build(:thing, :name => "thingamabob", :color => :yellow)
37
+ things << Factory.build(:thing, :name => "whatchamacallit", :color => :red)
38
+ things << Factory.build(:thing, :name => "gizmo", :color => :blue)
39
+ @comp.print_contents(:p, things, things.first.attributes.keys).should == "<p>thingamabob</p>\n<p>yellow</p>\n<p>whatchamacallit</p>\n<p>red</p>\n<p>gizmo</p>\n<p>blue</p>\n"
40
+ end
41
+
42
+ it "should print multiple contents, each item being paired with its own method and wrap each of the method outputs with a tag" do
43
+ accomodations = []
44
+ accomodations << mock(:hostel)
45
+ accomodations << mock(:campground)
46
+ accomodations[0].should_receive(:rooms).and_return(15)
47
+ accomodations[1].should_receive(:campsites).and_return(25)
48
+ capacity_information = [
49
+ Proc.new {|h| "There are #{h.rooms} hostel rooms available."},
50
+ Proc.new {|c| "The campground can take #{c.campsites} tents."}
51
+ ]
52
+ @comp.print_contents(:p, accomodations, capacity_information, :couple_methods_with_contents).should == "<p>There are 15 hostel rooms available.</p>\n<p>The campground can take 25 tents.</p>\n"
53
+ end
54
+
55
+ it "should merge attributes"
56
+
57
+ end
58
+
59
+ describe "wrap_contents" do
60
+ it "should wrap text content with a tag" do
61
+ content = "content"
62
+ @comp.print_contents(:span, content, nil, :wrap_whole_content).should == "<span>\n content\n</span>\n"
63
+ end
64
+
65
+ it "should wrap HTML content with a tag" do
66
+ content = "<span>\n content\n</span>\n"
67
+ @comp.wrap_contents(:p, content).should == "<p>\n <span>\n content\n </span>\n</p>\n"
68
+ end
69
+
70
+ it "should wrap multiple contents with a tag" do
71
+ names_list = ["<li>Nora</li>", "<li>Leon</li>", "<li>Robert</li>"]
72
+ @comp.wrap_contents(:ul, names_list).should == "<ul>\n <li>Nora</li>\n <li>Leon</li>\n <li>Robert</li>\n</ul>\n"
73
+ end
74
+
75
+ it "should print content using a method and wrap the output with a tag" do
76
+ deep_thought = mock :content
77
+ deep_thought.should_receive(:question).once.and_return(42)
78
+ @comp.wrap_contents("div.the-answer", deep_thought, :question).should == "<div class='the-answer'>\n 42\n</div>\n"
79
+ end
80
+
81
+ it "should print multiple contents using a method and wrap the whole output into a tag" do
82
+ names = %w(Aron Noel Trebor)
83
+ transformation = Proc.new {|x| x.downcase.reverse.humanize}
84
+ @comp.wrap_contents(:p, names, transformation).should == "<p>\n Nora\n Leon\n Robert\n</p>\n"
85
+ end
86
+
87
+ it "should wrap multiple contents with a tag, each item being printed using its own method" do
88
+ accomodations = []
89
+ accomodations << mock(:hostel)
90
+ accomodations << mock(:campground)
91
+ accomodations[0].should_receive(:rooms).and_return(15)
92
+ accomodations[1].should_receive(:campsites).and_return(25)
93
+ capacity_information = [
94
+ Proc.new {|h| "There are #{h.rooms} hostel rooms available."},
95
+ Proc.new {|c| "The campground can take #{c.campsites} tents."}
96
+ ]
97
+ @comp.wrap_contents(:p, accomodations, capacity_information, :couple_methods_with_contents).should == "<p>\n There are 15 hostel rooms available.\n The campground can take 25 tents.\n</p>\n"
98
+ end
99
+ end
100
+
101
+ describe "print_object" do
102
+ it "should print primitive data" do
103
+ for primitive in ["test", 1.0, 7, true, '']
104
+ @comp.print_object(primitive).should == primitive.to_s
105
+ end
106
+ end
107
+
108
+ it "should call callable objects" do
109
+ callable = mock :method
110
+ callable.should_receive(:call).at_least(:once).and_return(42)
111
+ @comp.print_object(callable).should == callable.call.to_s
112
+ end
113
+
114
+ it "should render components" do
115
+ renderable = mock :component
116
+ html = "<div>\n Component content\n</div"
117
+ renderable.should_receive(:to_html).any_number_of_times.and_return(html)
118
+ renderable.should_receive(:to_s).any_number_of_times.and_return(html)
119
+ @comp.print_object(renderable).should == html
120
+ end
121
+
122
+ it "should capture Haml buffers" do
123
+ p = lambda { @comp.haml_tag(:span, "haml") }
124
+ @comp.print_object(p).should == "<span>haml</span>\n"
125
+ p = lambda { @comp.haml_concat("written-to-buffer-and-captured") }
126
+ @comp.print_object(p).should == "written-to-buffer-and-captured\n"
127
+ end
128
+
129
+ it "should utilize receiver capabilities of object if applicable" do
130
+ receiver = mock :object
131
+ receiver.should_receive(:message).at_least(:once).and_return(42)
132
+ @comp.print_object(receiver, :message).should == receiver.message.to_s
133
+ end
134
+
135
+ it "should yield non-receiver objects if a suitable method is given" do
136
+ callable = mock :method
137
+ object = 42
138
+ callable.should_receive(:arity).at_least(:once).and_return(1, -1, -2)
139
+ callable.should_receive(:call).with(object).at_least(:once).and_return(42)
140
+ 3.times do
141
+ @comp.print_object(object, callable).should == callable.call(object).to_s
142
+ end
143
+ end
144
+
145
+ it "should not try to print non-receiver objects if an unsuitable method is given" do
146
+ callable = mock :method
147
+ object = 42
148
+ callable.should_receive(:arity).at_least(:once).and_return(0, 2, -3)
149
+ callable.should_not_receive(:call)
150
+ 3.times do
151
+ lambda {@comp.print_object(object, callable)}.should raise_error(ArgumentError)
152
+ end
153
+ end
154
+ end
155
+
156
+ end