Commit 5ca65170e5ee54ea9ab4b11fd6005de0ef124fea

Authored by Leandro Santos
Committed by Joenio Costa
1 parent 5b1eb2d2

Add a block to display the categories as a website menu.

(ActionItem1543)
app/controllers/admin/environment_design_controller.rb
... ... @@ -3,7 +3,7 @@ class EnvironmentDesignController < BoxOrganizerController
3 3 protect 'edit_environment_design', :environment
4 4  
5 5 def available_blocks
6   - @available_blocks ||= [ ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock ]
  6 + @available_blocks ||= [ ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock ]
7 7 end
8 8  
9 9 end
... ...
app/helpers/application_helper.rb
... ... @@ -985,4 +985,38 @@ module ApplicationHelper
985 985 number_to_currency(value, :unit => environment.currency_unit, :separator => environment.currency_separator, :delimiter => environment.currency_delimiter, :format => "%u %n")
986 986 end
987 987  
  988 + def collapsed_item_icon
  989 + "<span class='ui-icon ui-icon-circlesmall-plus' style='float:left;'></span>"
  990 + end
  991 + def expanded_item_icon
  992 + "<span class='ui-icon ui-icon-circlesmall-minus' style='float:left;'></span>"
  993 + end
  994 + def leaf_item_icon
  995 + "<span class='ui-icon ui-icon-arrow-1-e' style='float:left;'></span>"
  996 + end
  997 +
  998 + def display_category_menu(block, categories, root = true)
  999 + categories = categories.sort{|x,y| x.name <=> y.name}
  1000 + return "" if categories.blank?
  1001 + content_tag(:ul,
  1002 + categories.map do |category|
  1003 + category_path = category.kind_of?(ProductCategory) ? {:controller => 'search', :action => 'assets', :asset => 'products', :product_category => category.id} : { :controller => 'search', :action => 'category_index', :category_path => category.explode_path }
  1004 + category.display_in_menu? ?
  1005 + content_tag(:li,
  1006 + ( !category.is_leaf_displayable_in_menu? ? content_tag(:a, collapsed_item_icon, :href => "#", :id => "block_#{block.id}_category_#{category.id}", :class => 'category-link-expand ' + (root ? 'category-root' : 'category-no-root'), :onclick => "expandCategory(#{block.id}, #{category.id}); return false", :style => 'display: none') : leaf_item_icon) +
  1007 + link_to(content_tag(:span, category.name, :class => 'category-name'), category_path, :class => ("category-leaf" if category.is_leaf_displayable_in_menu?)) +
  1008 + content_tag(:div, display_category_menu(block, category.children, false), :id => "block_#{block.id}_category_content_#{category.id}", :class => 'child-category')
  1009 + ) : ''
  1010 + end
  1011 + ) +
  1012 + content_tag(:p) +
  1013 + (root ? javascript_tag("
  1014 + jQuery('.child-category').hide();
  1015 + jQuery('.category-link-expand').show();
  1016 + var expanded_icon = \"#{ expanded_item_icon }\";
  1017 + var collapsed_icon = \"#{ collapsed_item_icon }\";
  1018 + var category_expanded = { 'block' : 0, 'category' : 0 };
  1019 + ") : '')
  1020 + end
  1021 +
988 1022 end
... ...
app/models/categories_block.rb 0 → 100644
... ... @@ -0,0 +1,38 @@
  1 +class CategoriesBlock < Block
  2 +
  3 + CATEGORY_TYPES = {
  4 + _('Generic category') => nil,
  5 + _('Region') => 'Region',
  6 + _('Product') => 'ProductCategory'
  7 + }
  8 +
  9 + settings_items :category_types, :type => Array, :default => []
  10 +
  11 + def self.description
  12 + _("Categories Menu")
  13 + end
  14 +
  15 + def default_title
  16 + _("Categories Menu")
  17 + end
  18 +
  19 + def help
  20 + _('This block presents the categories like a web site menu.')
  21 + end
  22 +
  23 + def available_category_types
  24 + CATEGORY_TYPES
  25 + end
  26 +
  27 + def selected_categories
  28 + Category.top_level_for(self.owner).from_types(self.category_types)
  29 + end
  30 +
  31 + def content
  32 + block = self
  33 + lambda do
  34 + render :file => 'blocks/categories', :locals => { :block => block }
  35 + end
  36 + end
  37 +
  38 +end
... ...
app/models/category.rb
... ... @@ -9,9 +9,9 @@ class Category &lt; ActiveRecord::Base
9 9 validates_uniqueness_of :display_color, :scope => :environment_id, :if => (lambda { |cat| ! cat.display_color.nil? }), :message => N_('%{fn} was already assigned to another category.')
10 10  
11 11 # Finds all top level categories for a given environment.
12   - def self.top_level_for(environment)
13   - self.find(:all, :conditions => ['parent_id is null and environment_id = ?', environment.id ])
14   - end
  12 + named_scope :top_level_for, lambda { |environment|
  13 + {:conditions => ['parent_id is null and environment_id = ?', environment.id ]}
  14 + }
15 15  
16 16 acts_as_filesystem
17 17  
... ... @@ -30,6 +30,12 @@ class Category &lt; ActiveRecord::Base
30 30  
31 31 acts_as_having_image
32 32  
  33 + named_scope :from_types, lambda { |types|
  34 + types.select{ |t| t.blank? }.empty? ?
  35 + { :conditions => { :type => types } } :
  36 + { :conditions => [ "type IN (?) OR type IS NULL", types.reject{ |t| t.blank? } ] }
  37 + }
  38 +
33 39 def recent_articles(limit = 10)
34 40 self.articles.recent(limit)
35 41 end
... ... @@ -58,4 +64,9 @@ class Category &lt; ActiveRecord::Base
58 64 results
59 65 end
60 66  
  67 + def is_leaf_displayable_in_menu?
  68 + return false if self.display_in_menu == false
  69 + self.children.find(:all, :conditions => {:display_in_menu => true}).empty?
  70 + end
  71 +
61 72 end
... ...
app/views/blocks/categories.rhtml 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<%= block_title(block.title) %>
  2 +
  3 +<%= display_category_menu block, block.selected_categories %>
... ...
app/views/box_organizer/_categories_block.rhtml 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +<strong><%= _('Category types') %></strong>
  2 +
  3 +<% @block.available_category_types.each do |type_label, type_value| %>
  4 + <p><%= labelled_check_box(type_label, 'block[category_types][]', type_value.to_s, @block.category_types.include?(type_value.to_s)) %></p>
  5 +<% end %>
... ...
features/categories_block.feature 0 → 100644
... ... @@ -0,0 +1,86 @@
  1 +Feature: categories_block
  2 + As an admin
  3 + I want to manage the categories block
  4 +
  5 + Background:
  6 + Given I am on the homepage
  7 + And the following product_categories
  8 + | name | display_in_menu |
  9 + | Food | true |
  10 + | Book | true |
  11 + And the following product_categories
  12 + | parent | name | display_in_menu |
  13 + | Food | Vegetarian | true |
  14 + | Food | Steak | true |
  15 + | Book | Fiction | false |
  16 + | Book | Literature | true |
  17 + And the following categories
  18 + | name | display_in_menu |
  19 + | Wood | true |
  20 + And the following regions
  21 + | name | display_in_menu |
  22 + | Bahia | true |
  23 + And the following blocks
  24 + | owner | type |
  25 + | environment | CategoriesBlock |
  26 + And I am logged in as admin
  27 +
  28 + @selenium
  29 + Scenario: List just product categories
  30 + Given I go to /admin/environment_design
  31 + And I follow "Edit" within ".categories-block"
  32 + And I check "Product"
  33 + When I press "Save"
  34 + Then I should see "Food"
  35 + And I should see "Book"
  36 + And I should not see "Vegetarian"
  37 + And I should not see "Steak"
  38 + And I should not see "Fiction"
  39 +
  40 + @selenium
  41 + Scenario: Show submenu if it exists
  42 + Given I go to /admin/environment_design
  43 + And I follow "Edit" within ".categories-block"
  44 + And I check "Product"
  45 + And I press "Save"
  46 + Then I should see "Food"
  47 + And I should see "Book"
  48 + And I should not see "Vegetarian"
  49 + And I should not see "Steak"
  50 + And I should not see "Literature"
  51 + When I click ".category-link-expand category-root"
  52 + Then I should see "Literature"
  53 + When I click ".category-link-expand category-root"
  54 + Then I should see "Vegetarian"
  55 + And I should see "Steak"
  56 + And I should not see "Fiction"
  57 +
  58 + @selenium
  59 + Scenario: Show only one submenu per time
  60 + Given I go to /admin/environment_design
  61 + And I follow "Edit" within ".categories-block"
  62 + And I check "Product"
  63 + And I press "Save"
  64 + Then I should see "Food"
  65 + And I should not see "Vegetarian"
  66 + And I should not see "Steak"
  67 + When I click ".category-link-expand category-root"
  68 + Then I should see "Vegetarian"
  69 + And I should see "Steak"
  70 +
  71 + @selenium
  72 + Scenario: List just general categories
  73 + Given I go to /admin/environment_design
  74 + And I follow "Edit" within ".categories-block"
  75 + And I check "Generic Category"
  76 + When I press "Save"
  77 + Then I should see "Wood"
  78 +
  79 + @selenium
  80 + Scenario: List just regions
  81 + Given I go to /admin/environment_design
  82 + And I follow "Edit" within ".categories-block"
  83 + And I check "Region"
  84 + When I press "Save"
  85 + Then I should see "Bahia"
  86 +
... ...
features/step_definitions/noosfero_steps.rb
... ... @@ -21,7 +21,12 @@ end
21 21 Given /^the following blocks$/ do |table|
22 22 table.hashes.map{|item| item.dup}.each do |item|
23 23 klass = item.delete('type')
24   - owner = Profile[item.delete('owner')]
  24 + owner_type = item.delete('owner')
  25 + owner = owner_type == 'environment' ? Environment.default : Profile[owner_type]
  26 + if owner.boxes.empty?
  27 + owner.boxes<< Box.new
  28 + owner.boxes.first.blocks << MainBlock.new
  29 + end
25 30 box_id = owner.boxes.last.id
26 31 klass.constantize.create!(item.merge(:box_id => box_id))
27 32 end
... ...
public/javascripts/application.js
... ... @@ -193,3 +193,24 @@ function render_jquery_ui_buttons(element_id) {
193 193 })
194 194 }
195 195 }
  196 +
  197 +function expandCategory(block, id) {
  198 + var link = jQuery('#block_' + block + '_category_' + id);
  199 + if (category_expanded['block'] > 0 && category_expanded['category'] > 0 && category_expanded['block'] == block && category_expanded['category'] != id && link.hasClass('category-root')) {
  200 + expandCategory(category_expanded['block'], category_expanded['category']);
  201 + category_expanded['category'] = id;
  202 + category_expanded['block'] = block;
  203 + }
  204 + if (category_expanded['block'] == 0) category_expanded['block'] = block;
  205 + if (category_expanded['category'] == 0) category_expanded['category'] = id;
  206 + jQuery('#block_' + block + '_category_content_' + id).slideToggle('slow');
  207 + link.toggleClass('category-expanded');
  208 + if (link.hasClass('category-expanded')) link.html(expanded_icon);
  209 + else {
  210 + link.html(collapsed_icon);
  211 + if (link.hasClass('category-root')) {
  212 + category_expanded['block'] = 0;
  213 + category_expanded['category'] = 0;
  214 + }
  215 + }
  216 +}
... ...
public/stylesheets/application.css
... ... @@ -4051,3 +4051,35 @@ h1#agenda-title {
4051 4051 .treeitem .button{
4052 4052 display: inline;
4053 4053 }
  4054 +
  4055 +/* Categories block stuff */
  4056 +
  4057 +.categories-block ul {
  4058 + margin: 0;
  4059 + padding: 0;
  4060 + float: left;
  4061 +}
  4062 +.categories-block li {
  4063 + display: block;
  4064 + float: left;
  4065 + width: 100%;
  4066 +}
  4067 +.categories-block ul a {
  4068 + text-decoration: none;
  4069 + background-color: transparent;
  4070 + border-bottom: 1px solid #fff;
  4071 + border-color: #fff;
  4072 + line-height: 22px;
  4073 +}
  4074 +.categories-block div {
  4075 + clear: both;
  4076 +}
  4077 +.categories-block p {
  4078 + clear: both;
  4079 +}
  4080 +.categories-block li a:hover {
  4081 + text-decoration: none;
  4082 +}
  4083 +.categories-block .ui-icon {
  4084 + margin-top: 2px;
  4085 +}
... ...
test/functional/environment_design_controller_test.rb
... ... @@ -6,7 +6,7 @@ class EnvironmentDesignController; def rescue_action(e) raise e end; end
6 6  
7 7 class EnvironmentDesignControllerTest < Test::Unit::TestCase
8 8  
9   - ALL_BLOCKS = [ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock ]
  9 + ALL_BLOCKS = [ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock ]
10 10  
11 11 def setup
12 12 @controller = EnvironmentDesignController.new
... ...
test/unit/categories_block_test.rb 0 → 100644
... ... @@ -0,0 +1,42 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class CategoriesBlockTest < ActiveSupport::TestCase
  4 +
  5 + should 'default describe' do
  6 + assert_not_equal Block.description, CategoriesBlock.description
  7 + end
  8 +
  9 + should 'default title' do
  10 + block = Block.new
  11 + category_block = CategoriesBlock.new
  12 + assert_not_equal block.title, category_block.default_title
  13 + end
  14 +
  15 + should 'have a help defined' do
  16 + category_block = CategoriesBlock.new
  17 + assert_not_nil category_block.help
  18 + end
  19 +
  20 + should 'display category block' do
  21 + block = CategoriesBlock.new
  22 +
  23 + self.expects(:render).with(:file => 'blocks/categories', :locals => { :block => block})
  24 + instance_eval(& block.content)
  25 + end
  26 +
  27 + should 'be editable' do
  28 + assert CategoriesBlock.new.editable?
  29 + end
  30 +
  31 + should 'default category types is an empty array' do
  32 + category_block = CategoriesBlock.new
  33 + assert_kind_of Array, category_block.category_types
  34 + assert category_block.category_types.empty?
  35 + end
  36 +
  37 + should 'available category types' do
  38 + category_block = CategoriesBlock.new
  39 + assert_equal({ _('Generic category') => nil, _('Region') => 'Region', _('Product') => 'ProductCategory' }, category_block.available_category_types)
  40 + end
  41 +
  42 +end
... ...
test/unit/category_test.rb
... ... @@ -389,4 +389,50 @@ class CategoryTest &lt; Test::Unit::TestCase
389 389 assert Category.new.accept_products?
390 390 end
391 391  
  392 + should 'get categories by type including nil' do
  393 + category = Category.create!(:name => 'test category', :environment => Environment.default)
  394 + region = Region.create!(:name => 'test region', :environment => Environment.default)
  395 + product = ProductCategory.create!(:name => 'test product', :environment => Environment.default)
  396 + result = Category.from_types(['ProductCategory', '']).all
  397 + assert_equal 2, result.size
  398 + assert result.include?(product)
  399 + assert result.include?(category)
  400 + end
  401 +
  402 + should 'get categories by type and not nil' do
  403 + category = Category.create!(:name => 'test category', :environment => Environment.default)
  404 + region = Region.create!(:name => 'test region', :environment => Environment.default)
  405 + product = ProductCategory.create!(:name => 'test product', :environment => Environment.default)
  406 + result = Category.from_types(['Region', 'ProductCategory']).all
  407 + assert_equal 2, result.size
  408 + assert result.include?(region)
  409 + assert result.include?(product)
  410 + end
  411 +
  412 + should 'define a leaf to be displayed in menu' do
  413 + c1 = fast_create(Category, :display_in_menu => true)
  414 + c11 = fast_create(Category, :display_in_menu => true, :parent_id => c1.id)
  415 + c2 = fast_create(Category, :display_in_menu => true)
  416 + c21 = fast_create(Category, :display_in_menu => false, :parent_id => c2.id)
  417 + c22 = fast_create(Category, :display_in_menu => false, :parent_id => c2.id)
  418 +
  419 + assert_equal false, c1.is_leaf_displayable_in_menu?
  420 + assert_equal true, c11.is_leaf_displayable_in_menu?
  421 + assert_equal true, c2.is_leaf_displayable_in_menu?
  422 + assert_equal false, c21.is_leaf_displayable_in_menu?
  423 + assert_equal false, c22.is_leaf_displayable_in_menu?
  424 + end
  425 +
  426 + should 'filter top_level categories by type' do
  427 + toplevel_productcategory = fast_create(ProductCategory)
  428 + leaf_productcategory = fast_create(ProductCategory, :parent_id => toplevel_productcategory.id)
  429 +
  430 + toplevel_category = fast_create(Category)
  431 + leaf_category = fast_create(Category, :parent_id => toplevel_category.id)
  432 +
  433 + assert_includes Category.top_level_for(Environment.default).from_types(['ProductCategory']), toplevel_productcategory
  434 + assert_not_includes Category.top_level_for(Environment.default).from_types(['ProductCategory']), leaf_productcategory
  435 + assert_not_includes Category.top_level_for(Environment.default).from_types(['ProductCategory']), toplevel_category
  436 + end
  437 +
392 438 end
... ...