active_component 0.1.2

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