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,930 @@
1
+ # encoding: utf-8
2
+
3
+ module ActiveComponent
4
+ class Base
5
+ include ActiveComponent
6
+ include Haml::Helpers
7
+ include Enumerable
8
+
9
+ # Rails XSS protection support
10
+ include Haml::Helpers::XssMods
11
+ #require 'forwardable'
12
+ #extend ::Forwardable
13
+
14
+ attr_accessor :attributes, :title
15
+
16
+ # Initializes component by fetching arguments of a flexible method call as well as initializing the node and buffer
17
+ # *Example*
18
+ # def initialize(*args, &content_block)
19
+ # fetch_args(args, [:content, :title, :special_param, :attributes], &content_block)
20
+ #
21
+ # # Set defaults afterwards
22
+ # @attributes ||= {:class => @title}
23
+ # end
24
+ #
25
+ # Arguments may be non-hash objects with certain order.
26
+ # Then, the arguments will be set to instance variables with the var_names entry at the same index.
27
+ # Though, it is always possible use a hash for assigning parameters to keywords (e.g. :title => "Blumenkübel");
28
+ # As always, parenthesis can be omitted for this last hash.
29
+ #
30
+ # The list of variable names will be iterated in order.
31
+ # The first element becomes an instance variable that gets the block assigned (if passed along).
32
+ # If the list of variable names iteration is complete, remaining key-value pairs of the Hash part of the arguments list are merged into @attributes.
33
+ #
34
+ # Thus, all of the following signatures are legal for the **sender of fetch_args**:
35
+ # *Example 1*
36
+ # new("content", "title", :class => "api")
37
+ #
38
+ # *Example 2*
39
+ # new(:class => "api", :title => "title") { content }
40
+ #
41
+ # *Example 3*
42
+ # new("content", {:attributes => {:class => "api"}, :title => "title"})
43
+ #
44
+ # @param args [Array<Object>] Argument list where to fetch from
45
+ # @param var_names [Array<Symbol>] Ordered list of instance variables to fetch. First one gets assigned to block (if given).
46
+ # @param &content_block [#to_proc] The given block; will be assigned to variable named first in +var_names+.
47
+ def init_component(args, var_names = [:content, :title, :attributes], &content_block)
48
+
49
+ init_node
50
+ init_buffer
51
+
52
+ # Fetch arguments
53
+ non_hash_args = []
54
+ args_hash = {}
55
+ # Collect all non-hash args and merge all hashs together
56
+ for arg in args
57
+ arg.is_a?(Hash) ? args_hash.merge!(arg) : non_hash_args << arg
58
+ end
59
+
60
+ # var_names.first is set to block if block given
61
+ send(var_names.shift.to_s + "=", content_block.call) if content_block
62
+
63
+ for var_name in var_names
64
+ # Each value is extracted from args_hash, if resp. var_name present, otherwise the next non-hash argument is taken
65
+ send var_name.to_s + "=", (args_hash.delete(var_name) or non_hash_args.shift)
66
+ end
67
+
68
+ @attributes ||= {}
69
+ # All args in args_hash that have not been used for setting an instance variable become attributes.
70
+ @attributes.set_defaults!(args_hash)
71
+ # The class attribute will contain the component title and class_name (unless component is a html tag wrapper)
72
+ @attributes[:class] = (html_class + [@attributes[:class]].flatten).compact.uniq
73
+ end
74
+
75
+ def content=(cont)
76
+ @content = cont
77
+ # Add content as a child if it is also a component
78
+ cont.transmogrify do |c|
79
+ self << c if c.is_a? ActiveComponent
80
+ end
81
+ end
82
+
83
+ def content
84
+ # If content is not given yet, return node children
85
+ @content || children
86
+ end
87
+
88
+ def html_class
89
+ class_arr = []
90
+ class_arr << @title.hyphenize unless @title.blank?
91
+ class_arr << class_name unless is_html_tag_wrapper?
92
+ class_arr.uniq
93
+ end
94
+
95
+ def class_name
96
+ self.class.to_s.hyphenize
97
+ end
98
+
99
+ def to_html
100
+ raise NotImplementedError, "to_html has to be implemented for every component that inherits from ActiveComponent::Base"
101
+ end
102
+
103
+ def to_s
104
+ to_html
105
+ end
106
+
107
+ def is_html_tag_wrapper?
108
+ ActiveComponent::HTML5_ELEMENTS.each_value {|category| break true if category.include?(class_name.to_sym)} == true
109
+ end
110
+
111
+ def self.inherited(component_class)
112
+ def_component_helper(component_class) unless component_class.to_s =~ /#/
113
+ end
114
+
115
+ def self.def_component_helper(component_class)
116
+ raise ArgumentError, "Anonymous classes are not allowed because a name is needed." if component_class.to_s =~ /#/
117
+
118
+ ActiveComponent.class_eval do
119
+ # New way of defining methods with a block parameter (1.9 only)
120
+ # Attention: evaluation context seems to differ!
121
+ #define_method(component_class.to_s.underscore) do |*args, &block|
122
+ #component_class.new(*args, &block)
123
+ #end
124
+
125
+ # Old way of defining methods with a block parameter (supported by 1.8)
126
+ eval %(
127
+ def #{component_class.to_s.underscore}(*args, &block)
128
+ #{component_class}.new(*args, &block)
129
+ end
130
+ )
131
+ end
132
+ end
133
+
134
+ # This helper creates HTML wrapper components that become sub classes of the given super_class (e.g. Section)
135
+ def self.def_html_sub_components(names, super_class)
136
+ for name in names
137
+ # Creating an anonymous subclass and set according constant
138
+ new_component = Object.const_set(name.to_s.camelize, Class.new(super_class))
139
+ # Register component instantiation helper manually with the class constant
140
+ def_component_helper(new_component)
141
+
142
+ new_component.class_eval do
143
+ # FIXME: Remove eval wrap as soon as Ruby 1.9.2 support can be dropped
144
+ # Problem: "super from singleton method that is defined to multiple classes is not supported; this will be fixed in 1.9.3 or later"
145
+ # See https://gist.github.com/455547
146
+ eval %(
147
+ def initialize(*args, &block)
148
+ args << {:tag_type => self.class.to_s.underscore.to_sym}
149
+ super *args, &block
150
+ end
151
+ )
152
+ end
153
+ end
154
+ end
155
+
156
+ #----------------------------------------
157
+ # NODE METHODS COPIED FROM Tree::TreeNode
158
+ # An own, delegatable Tree Library has to
159
+ # be written. Until then, the methods are
160
+ # contained here as they make heavy use
161
+ # of self.
162
+ #----------------------------------------
163
+
164
+ # Overridden / own methods
165
+ #--------------------
166
+ # Adds the specified child node to the receiver node.
167
+ #
168
+ # This method can also be used for *grafting* a subtree into the receiver node's tree, if the specified child node
169
+ # is the root of a subtree (i.e., has child nodes under it).
170
+ #
171
+ # The receiver node becomes parent of the node passed in as the argument, and
172
+ # the child is added as the last child ("right most") in the current set of
173
+ # children of the receiver node.
174
+ #
175
+ # @param [Tree::TreeNode] child The child node to add.
176
+ #
177
+ # @return [Tree::TreeNode] The added child node.
178
+ #
179
+ # @raise [RuntimeError] This exception is raised if another child node with the same
180
+ # node_name exists.
181
+ # @raise [ArgumentError] This exception is raised if a +nil+ node is passed as the argument.
182
+ #
183
+ # @see #<<
184
+ def add(child, prepend = false)
185
+ raise ArgumentError, "Attempting to add a nil node" unless child
186
+ raise "Child #{child.node_name} already added!" if @childrenHash.has_key?(child.node_name)
187
+
188
+ @childrenHash[child.node_name] = child
189
+ prepend ? @children.unshift(child) : (@children << child)
190
+ raise "Great Scott! I just added a ghost child!" if !(@children.include?(child)) || @children.empty?
191
+ child.parent = self
192
+ child
193
+ end
194
+
195
+ def prepend(child)
196
+ add(child, true)
197
+ end
198
+
199
+ # Original Methods
200
+ #--------------------
201
+
202
+ # node_name of this node. Expected to be unique within the tree.
203
+ attr_accessor :node_name
204
+
205
+ # node_content of this node. Can be +nil+.
206
+ attr_accessor :node_content
207
+
208
+ # TODO: was not necessary to provide in Tree::TreeNode. Why here?
209
+ attr_accessor :childrenHash
210
+
211
+ # Parent of this node. Will be +nil+ for a root node.
212
+ attr_accessor :parent
213
+
214
+
215
+ # Creates a new node with a node_name and optional node_content.
216
+ # The node node_name is expected to be unique within the tree.
217
+ #
218
+ # The node_content can be of any type, and defaults to +nil+.
219
+ #
220
+ # @param [Object] node_name node_name of the node. Usual usage is to pass a String.
221
+ # @param [Object] node_content node_content of the node.
222
+ #
223
+ # @raise [ArgumentError] Raised if the node node_name is empty.
224
+ def init_node(node_name = object_id, node_content = nil)
225
+ raise ArgumentError, "Node node_name HAS to be provided!" if node_name == nil
226
+ @node_name, @node_content = node_name, node_content
227
+
228
+ self.setAsRoot!
229
+ @childrenHash = Hash.new
230
+ @children = []
231
+ end
232
+
233
+ # Returns a copy of the receiver node, with its parent and children links removed.
234
+ # The original node remains attached to its tree.
235
+ #
236
+ # @return [Tree::TreeNode] A copy of the receiver node.
237
+ def detached_copy
238
+ Tree::TreeNode.new(@node_name, @node_content ? @node_content.clone : nil)
239
+ end
240
+
241
+
242
+ # Returns an array of ancestors of the receiver node in reversed order
243
+ # (the first element is the immediate parent of the receiver).
244
+ #
245
+ # Returns +nil+ if the receiver is a root node.
246
+ #
247
+ # @return [Array, nil] An array of ancestors of the receiver node, or +nil+ if this is a root node.
248
+ def parentage
249
+ return nil if isRoot?
250
+
251
+ parentageArray = []
252
+ prevParent = self.parent
253
+ while (prevParent)
254
+ parentageArray << prevParent
255
+ prevParent = prevParent.parent
256
+ end
257
+
258
+ parentageArray
259
+ end
260
+
261
+ # Protected method to set the parent node for the receiver node.
262
+ # This method should *NOT* be invoked by client code.
263
+ #
264
+ # @param [Tree::TreeNode] parent The parent node.
265
+ #
266
+ # @return [Tree::TreeNode] The parent node.
267
+ def parent=(parent) # :nodoc:
268
+ @parent = parent
269
+ end
270
+
271
+ # Convenience synonym for {Tree::TreeNode#add} method.
272
+ #
273
+ # This method allows an easy mechanism to add node hierarchies to the tree
274
+ # on a given path via chaining the method calls to successive child nodes.
275
+ #
276
+ # @example Add a child and grand-child to the root
277
+ # root << child << grand_child
278
+ #
279
+ # @param [Tree::TreeNode] child the child node to add.
280
+ #
281
+ # @return [Tree::TreeNode] The added child node.
282
+ #
283
+ # @see Tree::TreeNode#add
284
+ def <<(child)
285
+ add(child)
286
+ end
287
+
288
+ # Adds the specified child node to the receiver node.
289
+ #
290
+ # This method can also be used for *grafting* a subtree into the receiver node's tree, if the specified child node
291
+ # is the root of a subtree (i.e., has child nodes under it).
292
+ #
293
+ # The receiver node becomes parent of the node passed in as the argument, and
294
+ # the child is added as the last child ("right most") in the current set of
295
+ # children of the receiver node.
296
+ #
297
+ # @param [Tree::TreeNode] child The child node to add.
298
+ #
299
+ # @return [Tree::TreeNode] The added child node.
300
+ #
301
+ # @raise [RuntimeError] This exception is raised if another child node with the same
302
+ # node_name exists.
303
+ # @raise [ArgumentError] This exception is raised if a +nil+ node is passed as the argument.
304
+ #
305
+ # @see #<<
306
+ # def add(child)
307
+ # raise ArgumentError, "Attempting to add a nil node" unless child
308
+ # raise "Child #{child.node_name} already added!" if @childrenHash.has_key?(child.node_name)
309
+ #
310
+ # @childrenHash[child.node_name] = child
311
+ # @children << child
312
+ # child.parent = self
313
+ # return child
314
+ # end
315
+
316
+ # Removes the specified child node from the receiver node.
317
+ #
318
+ # This method can also be used for *pruning* a sub-tree, in cases where the removed child node is
319
+ # the root of the sub-tree to be pruned.
320
+ #
321
+ # The removed child node is orphaned but accessible if an alternate reference exists. If accessible via
322
+ # an alternate reference, the removed child will report itself as a root node for its sub-tree.
323
+ #
324
+ # @param [Tree::TreeNode] child The child node to remove.
325
+ #
326
+ # @return [Tree::TreeNode] The removed child node, or +nil+ if a +nil+ was passed in as argument.
327
+ #
328
+ # @see #removeFromParent!
329
+ # @see #removeAll!
330
+ def remove!(child)
331
+ return nil unless child
332
+
333
+ @childrenHash.delete(child.node_name)
334
+ @children.delete(child)
335
+ child.setAsRoot!
336
+ child
337
+ end
338
+
339
+ # Removes the receiver node from its parent. The reciever node becomes the new root for its subtree.
340
+ #
341
+ # If this is the root node, then does nothing.
342
+ #
343
+ # @return [Tree:TreeNode] +self+ (the removed receiver node) if the operation is successful, +nil+ otherwise.
344
+ #
345
+ # @see #removeAll!
346
+ def removeFromParent!
347
+ @parent.remove!(self) unless isRoot?
348
+ end
349
+
350
+ # Removes all children from the receiver node. If an indepedent reference exists to the child
351
+ # nodes, then these child nodes report themselves as roots after this operation.
352
+ #
353
+ # @return [Tree::TreeNode] The receiver node (+self+)
354
+ #
355
+ # @see #remove!
356
+ # @see #removeFromParent!
357
+ def removeAll!
358
+ for child in @children
359
+ child.setAsRoot!
360
+ end
361
+ @childrenHash.clear
362
+ @children.clear
363
+ self
364
+ end
365
+
366
+ # Returns +true+ if the receiver node has node_content.
367
+ #
368
+ # @return [Boolean] +true+ if the node has node_content.
369
+ def hasnode_content?
370
+ @node_content != nil
371
+ end
372
+
373
+ # Protected method which sets the receiver node as a root node.
374
+ #
375
+ # @return +nil+.
376
+ def setAsRoot! # :nodoc:
377
+ @parent = nil
378
+ end
379
+
380
+ # Returns +true+ if the receiver is a root node. Note that
381
+ # orphaned children will also be reported as root nodes.
382
+ #
383
+ # @return [Boolean] +true+ if this is a root node.
384
+ def is_root?
385
+ @parent.nil?
386
+ end
387
+
388
+ alias :isRoot? :is_root?
389
+
390
+ # Returns +true+ if the receiver node has any child node.
391
+ #
392
+ # @return [Boolean] +true+ if child nodes exist.
393
+ #
394
+ # @see #isLeaf?
395
+ def hasChildren?
396
+ @children.length != 0
397
+ end
398
+
399
+ # Returns +true+ if the receiver node is a 'leaf' - i.e., one without
400
+ # any children.
401
+ #
402
+ # @return [Boolean] +true+ if this is a leaf node.
403
+ #
404
+ # @see #hasChildren?
405
+ def isLeaf?
406
+ !hasChildren?
407
+ end
408
+
409
+ # Returns an array of all the immediate children of the receiver node. The child nodes are ordered
410
+ # "left-to-right" in the returned array.
411
+ #
412
+ # If a block is given, yields each child node to the block traversing from left to right.
413
+ #
414
+ # @yield [child] Each child is passed to the block, if given
415
+ # @yieldparam [Tree::TreeNode] child Each child node.
416
+ #
417
+ # @return [Array<Tree::TreeNode>] An array of the child nodes, if no block is given.
418
+ def children
419
+ if block_given?
420
+ @children.each {|child| yield child}
421
+ else
422
+ @children
423
+ end
424
+ end
425
+
426
+ # Returns the first child of the receiver node.
427
+ #
428
+ # Will return +nil+ if no children are present.
429
+ #
430
+ # @return [Tree::TreeNode] The first child, or +nil+ if none is present.
431
+ def firstChild
432
+ children.first
433
+ end
434
+
435
+ # Returns the last child of the receiver node.
436
+ #
437
+ # Will return +nil+ if no children are present.
438
+ #
439
+ # @return [Tree::TreeNode] The last child, or +nil+ if none is present.
440
+ def lastChild
441
+ children.last
442
+ end
443
+
444
+ # Traverses each node (including the receiver node) of the (sub)tree rooted at this node
445
+ # by yielding the nodes to the specified block.
446
+ #
447
+ # The traversal is *depth-first* and from *left-to-right* in pre-ordered sequence.
448
+ #
449
+ # @yield [child] Each node is passed to the block.
450
+ # @yieldparam [Tree::TreeNode] child Each node.
451
+ #
452
+ # @see #preordered_each
453
+ # @see #breadth_each
454
+ def each(&block) # :yields: node
455
+ yield self
456
+ children { |child| child.each(&block) }
457
+ end
458
+
459
+ # Traverses the (sub)tree rooted at the receiver node in pre-ordered sequence.
460
+ # This is a synonym of {Tree::TreeNode#each}.
461
+ #
462
+ # @yield [child] Each child is passed to the block.
463
+ # @yieldparam [Tree::TreeNode] node Each node.
464
+ #
465
+ # @see #each
466
+ # @see #breadth_each
467
+ def preordered_each(&block) # :yields: node
468
+ each(&block)
469
+ end
470
+
471
+ # Performs breadth-first traversal of the (sub)tree rooted at the receiver node. The
472
+ # traversal at a given level is from *left-to-right*. The receiver node itself is the first
473
+ # node to be traversed.
474
+ #
475
+ # @yield [child] Each node is passed to the block.
476
+ # @yieldparam [Tree::TreeNode] node Each node.
477
+ #
478
+ # @see #preordered_each
479
+ # @see #breadth_each
480
+ def breadth_each(&block)
481
+ node_queue = [self] # Create a queue with self as the initial entry
482
+
483
+ # Use a queue to do breadth traversal
484
+ until node_queue.empty?
485
+ node_to_traverse = node_queue.shift
486
+ yield node_to_traverse
487
+ # Enqueue the children from left to right.
488
+ node_to_traverse.children { |child| node_queue.push child }
489
+ end
490
+ end
491
+
492
+ # Yields every leaf node of the (sub)tree rooted at the receiver node to the specified block.
493
+ #
494
+ # May yield this node as well if this is a leaf node.
495
+ # Leaf traversal is *depth-first* and *left-to-right*.
496
+ #
497
+ # @yield [node] Each leaf node is passed to the block.
498
+ # @yieldparam [Tree::TreeNode] node Each leaf node.
499
+ #
500
+ # @see #each
501
+ # @see #breadth_each
502
+ def each_leaf &block
503
+ self.each { |node| yield(node) if node.isLeaf? }
504
+ end
505
+
506
+ # Returns the requested node from the set of immediate children.
507
+ #
508
+ # If the argument is _numeric_, then the in-sequence array of children is accessed using
509
+ # the argument as the *index* (zero-based).
510
+ #
511
+ # If the argument is *NOT* _numeric_, then it is taken to be the *node_name* of the child node to be returned.
512
+ #
513
+ # An ArgumentError exception is raised if neither node_name nor an index is provided.
514
+ #
515
+ # @param [String|Number] node_name_or_index node_name of the child, or its positional index in the array of child nodes.
516
+ #
517
+ # @return [Tree::TreeNode] the requested child node. If the index in not in range, or the node_name is not
518
+ # present, then a +nil+ is returned.
519
+ #
520
+ # @raise [ArgumentError] Raised if neither node_name nor index is provided.
521
+ #
522
+ # @see #add
523
+ def [](node_name_or_index)
524
+ raise ArgumentError, "node_name_or_index needs to be provided!" if node_name_or_index == nil
525
+
526
+ if node_name_or_index.kind_of?(Integer)
527
+ @children[node_name_or_index] || @childrenHash[node_name_or_index]
528
+ else
529
+ @childrenHash[node_name_or_index]
530
+ end
531
+ end
532
+
533
+ # Returns the total number of nodes in this (sub)tree, including the receiver node.
534
+ #
535
+ # Size of the tree is defined as:
536
+ #
537
+ # Size:: Total number nodes in the subtree including the receiver node.
538
+ #
539
+ # @return [Number] Total number of nodes in this (sub)tree.
540
+ def size
541
+ @children.inject(1) {|sum, node| sum + node.size}
542
+ end
543
+
544
+ # Convenience synonym for {Tree::TreeNode#size}.
545
+ #
546
+ # @todo The semantic of length is probably unclear. Should return the node depth instead
547
+ # to reflect the path length.
548
+ #
549
+ # @deprecated This method node_name is ambiguous and may be removed. Use TreeNode#size instead.
550
+ #
551
+ # @return [Number] The total number of nodes in this (sub)tree.
552
+ # @see #size
553
+ def length
554
+ size()
555
+ end
556
+
557
+ # Pretty prints the (sub)tree rooted at the receiver node.
558
+ #
559
+ # @param [Number] level The indentation level (4 spaces) to start with.
560
+ def printTree(level = 0)
561
+
562
+ if isRoot?
563
+ print "*"
564
+ else
565
+ print "|" unless parent.isLastSibling?
566
+ print(' ' * (level - 1) * 4)
567
+ print(isLastSibling? ? "+" : "|")
568
+ print "---"
569
+ print(hasChildren? ? "+" : ">")
570
+ end
571
+
572
+ puts " #{node_name}"
573
+
574
+ children { |child| child.printTree(level + 1)}
575
+ end
576
+
577
+ # Returns root node for the (sub)tree to which the receiver node belongs.
578
+ #
579
+ # Note that a root node's root is itself (*beware* of any loop construct that may become infinite!)
580
+ #
581
+ # @todo We should perhaps return nil as root's root.
582
+ #
583
+ # @return [Tree::TreeNode] Root of the (sub)tree.
584
+ def root
585
+ root = self
586
+ root = root.parent while !root.isRoot?
587
+ root
588
+ end
589
+
590
+ # Returns the first sibling of the receiver node. If this is the root node, then returns
591
+ # itself.
592
+ #
593
+ # 'First' sibling is defined as follows:
594
+ # First sibling:: The left-most child of the receiver's parent, which may be the receiver itself
595
+ #
596
+ # @todo Fix the inconsistency of returning root as its first sibling, and returning
597
+ # a +nil+ array for siblings of the node.
598
+ #
599
+ # @return [Tree::TreeNode] The first sibling node.
600
+ #
601
+ # @see #isFirstSibling?
602
+ # @see #lastSibling
603
+ def firstSibling
604
+ isRoot? ? self : parent.children.first
605
+ end
606
+
607
+ # Returns +true+ if the receiver node is the first sibling at its level.
608
+ #
609
+ # @return [Boolean] +true+ if this is the first sibling.
610
+ #
611
+ # @see #isLastSibling?
612
+ # @see #firstSibling
613
+ def isFirstSibling?
614
+ firstSibling == self
615
+ end
616
+
617
+ # Returns the last sibling of the receiver node. If this is the root node, then returns
618
+ # itself.
619
+ #
620
+ # 'Last' sibling is defined as follows:
621
+ # Last sibling:: The right-most child of the receiver's parent, which may be the receiver itself
622
+ #
623
+ # @todo Fix the inconsistency of returning root as its last sibling, and returning
624
+ # a +nil+ array for siblings of the node.
625
+ #
626
+ # @return [Tree::TreeNode] The last sibling node.
627
+ #
628
+ # @see #isLastSibling?
629
+ # @see #firstSibling
630
+ def lastSibling
631
+ isRoot? ? self : parent.children.last
632
+ end
633
+
634
+ # Returns +true+ if the receiver node is the last sibling at its level.
635
+ #
636
+ # @return [Boolean] +true+ if this is the last sibling.
637
+ #
638
+ # @see #isFirstSibling?
639
+ # @see #lastSibling
640
+ def isLastSibling?
641
+ lastSibling == self
642
+ end
643
+
644
+ # Returns an array of siblings for the receiver node. The receiver node is excluded.
645
+ #
646
+ # If a block is provided, yields each of the sibling nodes to the block.
647
+ # The root always has +nil+ siblings.
648
+ #
649
+ # @todo Fix the inconsistency of returning root as its own first/last sibling, and returning
650
+ # a +nil+ array for siblings of the same root node.
651
+ # @todo Also fix the inconsistency of returning +nil+ for a root node, and an empty array for nodes
652
+ # which have no siblings.
653
+ #
654
+ # @yield [sibling] Each sibling is passed to the block.
655
+ # @yieldparam [Tree::TreeNode] sibling Each sibling node.
656
+ #
657
+ # @return [Array<Tree::TreeNode>] Array of siblings of this node.
658
+ #
659
+ # @see #firstSibling
660
+ # @see #lastSibling
661
+ def siblings
662
+ return nil if is_root?
663
+
664
+ if block_given?
665
+ for sibling in parent.children
666
+ yield sibling if sibling != self
667
+ end
668
+ else
669
+ siblings = []
670
+ parent.children {|my_sibling| siblings << my_sibling if my_sibling != self}
671
+ siblings
672
+ end
673
+ end
674
+
675
+ # Returns +true+ if the receiver node is the only child of its parent.
676
+ #
677
+ # As a special case, a root node will always return +true+.
678
+ #
679
+ # @return [Boolean] +true+ if this is the only child of its parent.
680
+ #
681
+ # @see #siblings
682
+ def isOnlyChild?
683
+ isRoot? ? true : parent.children.size == 1
684
+ end
685
+
686
+ # Returns the next sibling for the receiver node.
687
+ # The 'next' node is defined as the node to right of the receiver node.
688
+ #
689
+ # Will return +nil+ if no subsequent node is present, or if the receiver is a root node.
690
+ #
691
+ # @return [Tree::treeNode] the next sibling node, if present.
692
+ #
693
+ # @see #previousSibling
694
+ # @see #siblings
695
+ def nextSibling
696
+ return nil if isRoot?
697
+ if myidx = parent.children.index(self)
698
+ parent.children.at(myidx + 1)
699
+ end
700
+ end
701
+
702
+ # Returns the previous sibling of the receiver node.
703
+ # 'Previous' node is defined to be the node to left of the receiver node.
704
+ #
705
+ # Will return +nil+ if no predecessor node is present, or if the receiver is a root node.
706
+ #
707
+ # @return [Tree::treeNode] the previous sibling node, if present.
708
+ #
709
+ # @see #nextSibling
710
+ # @see #siblings
711
+ def previousSibling
712
+ return nil if isRoot?
713
+ if myidx = parent.children.index(self)
714
+ parent.children.at(myidx - 1) if myidx > 0
715
+ end
716
+ end
717
+
718
+ # Provides a comparision operation for the nodes.
719
+ #
720
+ # Comparision is based on the natural character-set ordering of the node node_name.
721
+ #
722
+ # @param [Tree::TreeNode] other The other node to compare against.
723
+ #
724
+ # @return [Number] +1 if this node is a 'successor', 0 if equal and -1 if this node is a 'predecessor'.
725
+ def <=>(other)
726
+ return +1 if other == nil
727
+ self.node_name <=> other.node_name
728
+ end
729
+
730
+ # Freezes all nodes in the (sub)tree rooted at the receiver node.
731
+ #
732
+ # The nodes become immutable after this operation. In effect, the entire tree's
733
+ # structure and node_contents become _read-only_ and cannot be changed.
734
+ def freezeTree!
735
+ each {|node| node.freeze}
736
+ end
737
+
738
+ # Returns a marshal-dump represention of the (sub)tree rooted at the receiver node.
739
+ def marshal_dump
740
+ self.collect { |node| node.createDumpRep }
741
+ end
742
+
743
+ # Creates a dump representation of the reciever node and returns the same as a hash.
744
+ def createDumpRep # :nodoc:
745
+ { :node_name => @node_name, :parent => (isRoot? ? nil : @parent.node_name), :node_content => Marshal.dump(@node_content)}
746
+ end
747
+
748
+ # Loads a marshalled dump of a tree and returns the root node of the
749
+ # reconstructed tree. See the Marshal class for additional details.
750
+ #
751
+ #
752
+ # @todo This method probably should be a class method. It currently clobbers self
753
+ # and makes itself the root.
754
+ #
755
+ def marshal_load(dumped_tree_array)
756
+ nodes = { }
757
+ for node_hash in dumped_tree_array do
758
+ node_name = node_hash[:node_name]
759
+ parent_node_name = node_hash[:parent]
760
+ node_content = Marshal.load(node_hash[:node_content])
761
+
762
+ if parent_node_name then
763
+ nodes[node_name] = current_node = Tree::TreeNode.new(node_name, node_content)
764
+ nodes[parent_node_name].add current_node
765
+ else
766
+ # This is the root node, hence initialize self.
767
+ initialize(node_name, node_content)
768
+
769
+ nodes[node_name] = self # Add self to the list of nodes
770
+ end
771
+ end
772
+ end
773
+
774
+ # Creates a JSON representation of this node including all it's children. This requires the JSON gem to be
775
+ # available, or else the operation fails with a warning message.
776
+ #
777
+ # @author Dirk Breuer (http://github.com/railsbros-dirk)
778
+ # @since 0.7.0
779
+ #
780
+ # @return The JSON representation of this subtree.
781
+ #
782
+ # @see Tree::TreeNode.json_create
783
+ # @see http://flori.github.com/json
784
+ def to_json(*a)
785
+ begin
786
+ require 'json'
787
+
788
+ json_hash = {
789
+ "node_name" => node_name,
790
+ "node_content" => node_content,
791
+ JSON.create_id => self.class.node_name
792
+ }
793
+
794
+ if hasChildren?
795
+ json_hash["children"] = children
796
+ end
797
+
798
+ return json_hash.to_json
799
+
800
+ rescue LoadError => e
801
+ warn "The JSON gem couldn't be loaded. Due to this we cannot serialize the tree to a JSON representation"
802
+ end
803
+ end
804
+
805
+ # Creates a Tree::TreeNode object instance from a given JSON Hash representation. This requires the JSON gem to be
806
+ # available, or else the operation fails with a warning message.
807
+ #
808
+ # @author Dirk Breuer (http://github.com/railsbros-dirk)
809
+ # @since 0.7.0
810
+ #
811
+ # @param [Hash] json_hash The JSON hash to convert from.
812
+ #
813
+ # @return [Tree::TreeNode] The created tree.
814
+ #
815
+ # @see #to_json
816
+ # @see http://flori.github.com/json
817
+ def self.json_create(json_hash)
818
+ begin
819
+ require 'json'
820
+
821
+ node = new(json_hash["node_name"], json_hash["node_content"])
822
+
823
+ json_hash["children"].each do |child|
824
+ node << child
825
+ end if json_hash["children"]
826
+
827
+ return node
828
+ rescue LoadError => e
829
+ warn "The JSON gem couldn't be loaded. Due to this we cannot serialize the tree to a JSON representation."
830
+ end
831
+ end
832
+
833
+ # Returns height of the (sub)tree from the receiver node. Height of a node is defined as:
834
+ #
835
+ # Height:: Length of the longest downward path to a leaf from the node.
836
+ #
837
+ # - Height from a root node is height of the entire tree.
838
+ # - The height of a leaf node is zero.
839
+ #
840
+ # @return [Number] Height of the node.
841
+ def nodeHeight
842
+ return 0 if isLeaf?
843
+ 1 + @children.collect { |child| child.nodeHeight }.max
844
+ end
845
+
846
+ # Returns depth of the receiver node in its tree. Depth of a node is defined as:
847
+ #
848
+ # Depth:: Length of the node's path to its root. Depth of a root node is zero.
849
+ #
850
+ # 'level' is an alias for this method.
851
+ #
852
+ # @return [Number] Depth of this node.
853
+ def nodeDepth
854
+ return 0 if isRoot?
855
+ 1 + parent.nodeDepth
856
+ end
857
+
858
+ alias level nodeDepth # Aliased level() method to the nodeDepth().
859
+
860
+ # Returns depth of the tree from the receiver node. A single leaf node has a depth of 1.
861
+ #
862
+ # This method is *DEPRECATED* and may be removed in the subsequent releases.
863
+ # Note that the value returned by this method is actually the:
864
+ #
865
+ # _height_ + 1 of the node, *NOT* the _depth_.
866
+ #
867
+ # For correct and conventional behavior, please use {Tree::TreeNode#nodeDepth} and
868
+ # {Tree::TreeNode#nodeHeight} methods instead.
869
+ #
870
+ # @return [Number] depth of the node.
871
+ # @deprecated This method returns an incorrect value. Use the 'nodeDepth' method instead.
872
+ #
873
+ # @see #nodeDepth
874
+ def depth
875
+ begin
876
+ require 'structured_warnings' # To enable a nice way of deprecating of the depth method.
877
+ warn DeprecatedMethodWarning, 'This method is deprecated. Please use nodeDepth() or nodeHeight() instead (bug # 22535)'
878
+ rescue LoadError
879
+ # Oh well. Will use the standard Kernel#warn. Behavior will be identical.
880
+ warn 'Tree::TreeNode#depth() method is deprecated. Please use nodeDepth() or nodeHeight() instead (bug # 22535)'
881
+ end
882
+
883
+ return 1 if isLeaf?
884
+ 1 + @children.collect { |child| child.depth }.max
885
+ end
886
+
887
+ # Returns breadth of the tree at the receiver node's level.
888
+ # A single node without siblings has a breadth of 1.
889
+ #
890
+ # Breadth is defined to be:
891
+ # Breadth:: Number of sibling nodes to this node + 1 (this node itself),
892
+ # i.e., the number of children the parent of this node has.
893
+ #
894
+ # @return [Number] breadth of the node's level.
895
+ def breadth
896
+ isRoot? ? 1 : parent.children.size
897
+ end
898
+
899
+ # Returns the incoming edge-count of the receiver node.
900
+ #
901
+ # In-degree is defined as:
902
+ # In-degree:: The number of edges arriving at the node (0 for root, 1 for all other nodes)
903
+ #
904
+ # - In-degree = 0 for a root or orphaned node
905
+ # - In-degree = 1 for a node which has a parent
906
+ #
907
+ # @return [Number] The in-degree of this node.
908
+ def in_degree
909
+ isRoot? ? 0 : 1
910
+ end
911
+
912
+ # Returns the outgoing edge-count of the receiver node.
913
+ #
914
+ # Out-degree is defined as:
915
+ # Out-degree:: The number of edges leaving the node (zero for leafs)
916
+ #
917
+ # @return [Number] The out-degree of this node.
918
+ def out_degree
919
+ isLeaf? ? 0 : children.size
920
+ end
921
+
922
+ protected :parent=, :setAsRoot!, :createDumpRep
923
+
924
+ protected
925
+ def buffer
926
+ @haml_buffer.buffer
927
+ end
928
+
929
+ end
930
+ end