arbre 0.0.1 → 1.0.0.rc1

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.
Files changed (49) hide show
  1. data/.DS_Store +0 -0
  2. data/.gitignore +3 -0
  3. data/Gemfile +10 -1
  4. data/README.rdoc +68 -2
  5. data/arbre.gemspec +2 -0
  6. data/lib/.DS_Store +0 -0
  7. data/lib/arbre.rb +18 -0
  8. data/lib/arbre/component.rb +22 -0
  9. data/lib/arbre/context.rb +81 -0
  10. data/lib/arbre/element.rb +182 -0
  11. data/lib/arbre/element/builder_methods.rb +92 -0
  12. data/lib/arbre/element_collection.rb +25 -0
  13. data/lib/arbre/html/attributes.rb +20 -0
  14. data/lib/arbre/html/class_list.rb +24 -0
  15. data/lib/arbre/html/document.rb +42 -0
  16. data/lib/arbre/html/html5_elements.rb +47 -0
  17. data/lib/arbre/html/tag.rb +175 -0
  18. data/lib/arbre/html/text_node.rb +35 -0
  19. data/lib/arbre/rails.rb +5 -0
  20. data/lib/arbre/rails/forms.rb +92 -0
  21. data/lib/arbre/rails/rendering.rb +17 -0
  22. data/lib/arbre/rails/template_handler.rb +15 -0
  23. data/lib/arbre/version.rb +1 -1
  24. data/spec/arbre/integration/html_spec.rb +241 -0
  25. data/spec/arbre/unit/component.rb +23 -0
  26. data/spec/arbre/unit/context_spec.rb +35 -0
  27. data/spec/arbre/unit/element_finder_methods_spec.rb +101 -0
  28. data/spec/arbre/unit/element_spec.rb +252 -0
  29. data/spec/arbre/unit/html/tag_attributes_spec.rb +60 -0
  30. data/spec/arbre/unit/html/tag_spec.rb +63 -0
  31. data/spec/arbre/unit/html/text_node_spec.rb +5 -0
  32. data/spec/rails/integration/forms_spec.rb +121 -0
  33. data/spec/rails/integration/rendering_spec.rb +73 -0
  34. data/spec/rails/rails_spec_helper.rb +45 -0
  35. data/spec/rails/stub_app/config/database.yml +3 -0
  36. data/spec/rails/stub_app/config/routes.rb +3 -0
  37. data/spec/rails/stub_app/db/schema.rb +3 -0
  38. data/spec/rails/stub_app/log/.gitignore +1 -0
  39. data/spec/rails/stub_app/public/favicon.ico +0 -0
  40. data/spec/rails/templates/arbre/_partial.arb +1 -0
  41. data/spec/rails/templates/arbre/empty.arb +0 -0
  42. data/spec/rails/templates/arbre/page_with_assignment.arb +1 -0
  43. data/spec/rails/templates/arbre/page_with_erb_partial.arb +3 -0
  44. data/spec/rails/templates/arbre/page_with_partial.arb +3 -0
  45. data/spec/rails/templates/arbre/simple_page.arb +8 -0
  46. data/spec/rails/templates/erb/_partial.erb +1 -0
  47. data/spec/spec_helper.rb +7 -0
  48. data/spec/support/bundle.rb +4 -0
  49. metadata +101 -45
Binary file
data/.gitignore CHANGED
@@ -2,3 +2,6 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ benchmarks
6
+ /.rvmrc
7
+ /tags
data/Gemfile CHANGED
@@ -1,4 +1,13 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in arbre.gemspec
4
3
  gemspec
4
+
5
+ group :test do
6
+ gem "rspec"
7
+ end
8
+
9
+ group :rails do
10
+ gem "rspec-rails"
11
+ gem 'combustion', '0.3.2'
12
+ gem "capybara"
13
+ end
@@ -1,3 +1,69 @@
1
- Arbre - Coming Soon. For now, checkout Active Admin:
1
+ = Arbre - Ruby Object Oriented HTML Views
2
2
 
3
- http://github.com/gregbell/active_admin
3
+ Arbre is the DOM implemented in Ruby. This project is primarily used as
4
+ the object oriented view layer in Active Admin.
5
+
6
+ == Simple Usage
7
+
8
+ A simple example of setting up an Arbre context and creating some html
9
+
10
+ html = Arbre::Context.new do
11
+ h2 "Why Arbre is awesome?"
12
+
13
+ ul do
14
+ li "The DOM is implemented in ruby"
15
+ li "You can create object oriented views"
16
+ li "Templates suck"
17
+ end
18
+ end
19
+
20
+ puts html.to_s #=> <h2>Why</h2><ul><li></li></ul>
21
+
22
+
23
+ == The DOM in Ruby
24
+
25
+ The purpose of Arbre is to leave the view as ruby objects as long
26
+ as possible. This allows OO Design to be used to implement the view layer.
27
+
28
+
29
+ html = Arbre::Context.new do
30
+ h2 "Why Arbre is awesome?"
31
+ end
32
+
33
+ html.children.size #=> 1
34
+ html.children.first #=> #<Arbre::HTML::H2>
35
+
36
+ == Components
37
+
38
+ The purpose of Arbre is to be able to create shareable and extendable HTML
39
+ components. To do this, you create a subclass of Arbre::Component.
40
+
41
+ For example:
42
+
43
+ class Panel < Arbre::Component
44
+ builder_method :panel
45
+
46
+ def build(title, attributes = {})
47
+ super(attributes)
48
+
49
+ h3(title, :class => "panel-title")
50
+ end
51
+ end
52
+
53
+ The builder_method defines the method that will be called to build this component
54
+ when using the DSL. The arguments passed into the builder_method will be passed
55
+ into the #build method for you.
56
+
57
+ You can now use this panel in any Arbre context:
58
+
59
+ html = Arbre::Context.new do
60
+ panel "Hello World", :id => "my-panel" do
61
+ span "Inside the panel"
62
+ end
63
+ end
64
+
65
+ html.to_s #=>
66
+ # <div class='panel' id="my-panel">
67
+ # <h3 class='panel-title'>Hello World</h3>
68
+ # <span>Inside the panel</span>
69
+ # </div>
@@ -16,4 +16,6 @@ Gem::Specification.new do |s|
16
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  s.require_paths = ["lib"]
19
+
20
+ s.add_dependency("activesupport", ">= 3.0.0")
19
21
  end
Binary file
@@ -1,2 +1,20 @@
1
+ require 'active_support/core_ext/string/output_safety'
2
+ require 'active_support/hash_with_indifferent_access'
3
+ require 'active_support/inflector'
4
+
1
5
  module Arbre
2
6
  end
7
+
8
+ require 'arbre/element'
9
+ require 'arbre/context'
10
+ require 'arbre/html/attributes'
11
+ require 'arbre/html/class_list'
12
+ require 'arbre/html/tag'
13
+ require 'arbre/html/text_node'
14
+ require 'arbre/html/document'
15
+ require 'arbre/html/html5_elements'
16
+ require 'arbre/component'
17
+
18
+ if defined?(Rails)
19
+ require 'arbre/rails'
20
+ end
@@ -0,0 +1,22 @@
1
+ module Arbre
2
+ class Component < Arbre::HTML::Div
3
+
4
+ # By default components render a div
5
+ def tag_name
6
+ 'div'
7
+ end
8
+
9
+ def initialize(*)
10
+ super
11
+ add_class default_class_name
12
+ end
13
+
14
+ protected
15
+
16
+ # By default, add a css class named after the ruby class
17
+ def default_class_name
18
+ self.class.name.demodulize.underscore
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,81 @@
1
+ require 'arbre/element'
2
+
3
+ module Arbre
4
+ class Context < Element
5
+
6
+ def initialize(assigns = {}, helpers = nil, &block)
7
+ @_assigns = assigns || {}
8
+ @_assigns.symbolize_keys!
9
+ @_helpers = helpers
10
+ @_current_arbre_element_buffer = [self]
11
+
12
+ super(self)
13
+ instance_eval &block if block_given?
14
+ end
15
+
16
+ def arbre_context
17
+ self
18
+ end
19
+
20
+ def assigns
21
+ @_assigns
22
+ end
23
+
24
+ def helpers
25
+ @_helpers
26
+ end
27
+
28
+ def indent_level
29
+ # A context does not increment the indent_level
30
+ super - 1
31
+ end
32
+
33
+ def bytesize
34
+ cached_html.bytesize
35
+ end
36
+ alias :length :bytesize
37
+
38
+ def respond_to?(method)
39
+ super || cached_html.respond_to?(method)
40
+ end
41
+
42
+ # Webservers treat Arbre::Context as a string. We override
43
+ # method_missing to delegate to the string representation
44
+ # of the html.
45
+ def method_missing(method, *args, &block)
46
+ if cached_html.respond_to? method
47
+ cached_html.send method, *args, &block
48
+ else
49
+ super
50
+ end
51
+ end
52
+
53
+ def current_arbre_element
54
+ @_current_arbre_element_buffer.last
55
+ end
56
+
57
+ def with_current_arbre_element(tag)
58
+ raise ArgumentError, "Can't be in the context of nil. #{@_current_arbre_element_buffer.inspect}" unless tag
59
+ @_current_arbre_element_buffer.push tag
60
+ yield
61
+ @_current_arbre_element_buffer.pop
62
+ end
63
+ alias_method :within, :with_current_arbre_element
64
+
65
+ private
66
+
67
+
68
+ # Caches the rendered HTML so that we don't re-render just to
69
+ # get the content lenght or to delegate a method to the HTML
70
+ def cached_html
71
+ if defined?(@cached_html)
72
+ @cached_html
73
+ else
74
+ html = to_s
75
+ @cached_html = html if html.length > 0
76
+ html
77
+ end
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,182 @@
1
+ require 'arbre/element/builder_methods'
2
+ require 'arbre/element_collection'
3
+
4
+ module Arbre
5
+
6
+ class Element
7
+ include BuilderMethods
8
+
9
+ attr_accessor :parent
10
+ attr_reader :children, :arbre_context
11
+
12
+ def initialize(arbre_context = Arbre::Context.new)
13
+ @arbre_context = arbre_context
14
+ @children = ElementCollection.new
15
+ end
16
+
17
+ def assigns
18
+ arbre_context.assigns
19
+ end
20
+
21
+ def helpers
22
+ arbre_context.helpers
23
+ end
24
+
25
+ def tag_name
26
+ @tag_name ||= self.class.name.demodulize.downcase
27
+ end
28
+
29
+ def build(*args, &block)
30
+ # Render the block passing ourselves in
31
+ append_return_block(block.call(self)) if block
32
+ end
33
+
34
+ def add_child(child)
35
+ return unless child
36
+
37
+ if child.is_a?(Array)
38
+ child.each{|item| add_child(item) }
39
+ return @children
40
+ end
41
+
42
+ # If its not an element, wrap it in a TextNode
43
+ unless child.is_a?(Element)
44
+ child = Arbre::HTML::TextNode.from_string(child)
45
+ end
46
+
47
+ if child.respond_to?(:parent)
48
+ # Remove the child
49
+ child.parent.remove_child(child) if child.parent
50
+ # Set ourselves as the parent
51
+ child.parent = self
52
+ end
53
+
54
+ @children << child
55
+ end
56
+
57
+ def remove_child(child)
58
+ child.parent = nil if child.respond_to?(:parent=)
59
+ @children.delete(child)
60
+ end
61
+
62
+ def <<(child)
63
+ add_child(child)
64
+ end
65
+
66
+ def children?
67
+ @children.any?
68
+ end
69
+
70
+ def parent=(parent)
71
+ @parent = parent
72
+ end
73
+
74
+ def parent?
75
+ !@parent.nil?
76
+ end
77
+
78
+ def ancestors
79
+ if parent?
80
+ [parent] + parent.ancestors
81
+ else
82
+ []
83
+ end
84
+ end
85
+
86
+ # TODO: Shouldn't grab whole tree
87
+ def find_first_ancestor(type)
88
+ ancestors.find{|a| a.is_a?(type) }
89
+ end
90
+
91
+ def content=(contents)
92
+ clear_children!
93
+ add_child(contents)
94
+ end
95
+
96
+ def get_elements_by_tag_name(tag_name)
97
+ elements = ElementCollection.new
98
+ children.each do |child|
99
+ elements << child if child.tag_name == tag_name
100
+ elements.concat(child.get_elements_by_tag_name(tag_name))
101
+ end
102
+ elements
103
+ end
104
+ alias_method :find_by_tag, :get_elements_by_tag_name
105
+
106
+ def get_elements_by_class_name(class_name)
107
+ elements = ElementCollection.new
108
+ children.each do |child|
109
+ elements << child if child.class_list =~ /#{class_name}/
110
+ elements.concat(child.get_elements_by_tag_name(tag_name))
111
+ end
112
+ elements
113
+ end
114
+ alias_method :find_by_class, :get_elements_by_class_name
115
+
116
+ def content
117
+ children.to_s
118
+ end
119
+
120
+ def html_safe
121
+ to_s
122
+ end
123
+
124
+ def indent_level
125
+ parent? ? parent.indent_level + 1 : 0
126
+ end
127
+
128
+ def each(&block)
129
+ [to_s].each(&block)
130
+ end
131
+
132
+ def to_str
133
+ to_s
134
+ end
135
+
136
+ def to_s
137
+ content
138
+ end
139
+
140
+ def +(element)
141
+ case element
142
+ when Element, ElementCollection
143
+ else
144
+ element = Arbre::HTML::TextNode.from_string(element)
145
+ end
146
+ ElementCollection.new([self]) + element
147
+ end
148
+
149
+ def to_ary
150
+ ElementCollection.new [self]
151
+ end
152
+ alias_method :to_a, :to_ary
153
+
154
+ private
155
+
156
+ # Resets the Elements children
157
+ def clear_children!
158
+ @children.clear
159
+ end
160
+
161
+ # Implements the method lookup chain. When you call a method that
162
+ # doesn't exist, we:
163
+ #
164
+ # 1. Try to call the method on the current DOM context
165
+ # 2. Return an assigned variable of the same name
166
+ # 3. Call the method on the helper object
167
+ # 4. Call super
168
+ #
169
+ def method_missing(name, *args, &block)
170
+ if current_arbre_element.respond_to?(name)
171
+ current_arbre_element.send name, *args, &block
172
+ elsif assigns && assigns.has_key?(name)
173
+ assigns[name]
174
+ elsif helpers.respond_to?(name)
175
+ helpers.send(name, *args, &block)
176
+ else
177
+ super
178
+ end
179
+ end
180
+
181
+ end
182
+ end
@@ -0,0 +1,92 @@
1
+ module Arbre
2
+ class Element
3
+
4
+ module BuilderMethods
5
+
6
+ def self.included(klass)
7
+ klass.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def builder_method(method_name)
13
+ BuilderMethods.class_eval <<-EOF, __FILE__, __LINE__
14
+ def #{method_name}(*args, &block)
15
+ insert_tag ::#{self.name}, *args, &block
16
+ end
17
+ EOF
18
+ end
19
+
20
+ end
21
+
22
+ def build_tag(klass, *args, &block)
23
+ tag = klass.new(arbre_context)
24
+ tag.parent = current_arbre_element
25
+
26
+ # If you passed in a block and want the object
27
+ if block_given? && block.arity > 0
28
+ # Set out context to the tag, and pass responsibility to the tag
29
+ with_current_arbre_element tag do
30
+ tag.build(*args, &block)
31
+ end
32
+ else
33
+ # Build the tag
34
+ tag.build(*args)
35
+
36
+ # Render the blocks contents
37
+ if block_given?
38
+ with_current_arbre_element tag do
39
+ append_return_block(yield)
40
+ end
41
+ end
42
+ end
43
+
44
+ tag
45
+ end
46
+
47
+ def insert_tag(klass, *args, &block)
48
+ tag = build_tag(klass, *args, &block)
49
+ current_arbre_element.add_child(tag)
50
+ tag
51
+ end
52
+
53
+ def current_arbre_element
54
+ arbre_context.current_arbre_element
55
+ end
56
+
57
+ def with_current_arbre_element(tag, &block)
58
+ arbre_context.with_current_arbre_element(tag, &block)
59
+ end
60
+ alias_method :within, :with_current_arbre_element
61
+
62
+ private
63
+
64
+ # Appends the value to the current DOM element if there are no
65
+ # existing DOM Children and it responds to #to_s
66
+ def append_return_block(tag)
67
+ return nil if current_arbre_element.children?
68
+
69
+ if appendable_tag?(tag)
70
+ current_arbre_element << Arbre::HTML::TextNode.from_string(tag.to_s)
71
+ end
72
+ end
73
+
74
+ # Returns true if the object should be converted into a text node
75
+ # and appended into the DOM.
76
+ def appendable_tag?(tag)
77
+ is_appendable = !tag.is_a?(Arbre::Element) && tag.respond_to?(:to_s)
78
+
79
+ # In ruby 1.9, Arraay.new.to_s prints out an empty array ("[]"). In
80
+ # Arbre, we append the return value of blocks to the output, which
81
+ # can cause empty arrays to show up within the output. To get
82
+ # around this, we check if the object responds to #empty?
83
+ if tag.respond_to?(:empty?) && tag.empty?
84
+ is_appendable = false
85
+ end
86
+
87
+ is_appendable
88
+ end
89
+ end
90
+
91
+ end
92
+ end