From 5ca65170e5ee54ea9ab4b11fd6005de0ef124fea Mon Sep 17 00:00:00 2001 From: Leandro Nunes dos Santos Date: Fri, 23 Jul 2010 16:13:24 -0300 Subject: [PATCH] Add a block to display the categories as a website menu. --- app/controllers/admin/environment_design_controller.rb | 2 +- app/helpers/application_helper.rb | 34 ++++++++++++++++++++++++++++++++++ app/models/categories_block.rb | 38 ++++++++++++++++++++++++++++++++++++++ app/models/category.rb | 17 ++++++++++++++--- app/views/blocks/categories.rhtml | 3 +++ app/views/box_organizer/_categories_block.rhtml | 5 +++++ features/categories_block.feature | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ features/step_definitions/noosfero_steps.rb | 7 ++++++- public/javascripts/application.js | 21 +++++++++++++++++++++ public/stylesheets/application.css | 32 ++++++++++++++++++++++++++++++++ test/functional/environment_design_controller_test.rb | 2 +- test/unit/categories_block_test.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ test/unit/category_test.rb | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 329 insertions(+), 6 deletions(-) create mode 100644 app/models/categories_block.rb create mode 100644 app/views/blocks/categories.rhtml create mode 100644 app/views/box_organizer/_categories_block.rhtml create mode 100644 features/categories_block.feature create mode 100644 test/unit/categories_block_test.rb diff --git a/app/controllers/admin/environment_design_controller.rb b/app/controllers/admin/environment_design_controller.rb index efefac0..024fe21 100644 --- a/app/controllers/admin/environment_design_controller.rb +++ b/app/controllers/admin/environment_design_controller.rb @@ -3,7 +3,7 @@ class EnvironmentDesignController < BoxOrganizerController protect 'edit_environment_design', :environment def available_blocks - @available_blocks ||= [ ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock ] + @available_blocks ||= [ ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock ] end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c43e955..8e2e792 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -985,4 +985,38 @@ module ApplicationHelper number_to_currency(value, :unit => environment.currency_unit, :separator => environment.currency_separator, :delimiter => environment.currency_delimiter, :format => "%u %n") end + def collapsed_item_icon + "" + end + def expanded_item_icon + "" + end + def leaf_item_icon + "" + end + + def display_category_menu(block, categories, root = true) + categories = categories.sort{|x,y| x.name <=> y.name} + return "" if categories.blank? + content_tag(:ul, + categories.map do |category| + 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 } + category.display_in_menu? ? + content_tag(:li, + ( !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) + + link_to(content_tag(:span, category.name, :class => 'category-name'), category_path, :class => ("category-leaf" if category.is_leaf_displayable_in_menu?)) + + content_tag(:div, display_category_menu(block, category.children, false), :id => "block_#{block.id}_category_content_#{category.id}", :class => 'child-category') + ) : '' + end + ) + + content_tag(:p) + + (root ? javascript_tag(" + jQuery('.child-category').hide(); + jQuery('.category-link-expand').show(); + var expanded_icon = \"#{ expanded_item_icon }\"; + var collapsed_icon = \"#{ collapsed_item_icon }\"; + var category_expanded = { 'block' : 0, 'category' : 0 }; + ") : '') + end + end diff --git a/app/models/categories_block.rb b/app/models/categories_block.rb new file mode 100644 index 0000000..6c71cd4 --- /dev/null +++ b/app/models/categories_block.rb @@ -0,0 +1,38 @@ +class CategoriesBlock < Block + + CATEGORY_TYPES = { + _('Generic category') => nil, + _('Region') => 'Region', + _('Product') => 'ProductCategory' + } + + settings_items :category_types, :type => Array, :default => [] + + def self.description + _("Categories Menu") + end + + def default_title + _("Categories Menu") + end + + def help + _('This block presents the categories like a web site menu.') + end + + def available_category_types + CATEGORY_TYPES + end + + def selected_categories + Category.top_level_for(self.owner).from_types(self.category_types) + end + + def content + block = self + lambda do + render :file => 'blocks/categories', :locals => { :block => block } + end + end + +end diff --git a/app/models/category.rb b/app/models/category.rb index 732ea4f..ad6605f 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -9,9 +9,9 @@ class Category < ActiveRecord::Base validates_uniqueness_of :display_color, :scope => :environment_id, :if => (lambda { |cat| ! cat.display_color.nil? }), :message => N_('%{fn} was already assigned to another category.') # Finds all top level categories for a given environment. - def self.top_level_for(environment) - self.find(:all, :conditions => ['parent_id is null and environment_id = ?', environment.id ]) - end + named_scope :top_level_for, lambda { |environment| + {:conditions => ['parent_id is null and environment_id = ?', environment.id ]} + } acts_as_filesystem @@ -30,6 +30,12 @@ class Category < ActiveRecord::Base acts_as_having_image + named_scope :from_types, lambda { |types| + types.select{ |t| t.blank? }.empty? ? + { :conditions => { :type => types } } : + { :conditions => [ "type IN (?) OR type IS NULL", types.reject{ |t| t.blank? } ] } + } + def recent_articles(limit = 10) self.articles.recent(limit) end @@ -58,4 +64,9 @@ class Category < ActiveRecord::Base results end + def is_leaf_displayable_in_menu? + return false if self.display_in_menu == false + self.children.find(:all, :conditions => {:display_in_menu => true}).empty? + end + end diff --git a/app/views/blocks/categories.rhtml b/app/views/blocks/categories.rhtml new file mode 100644 index 0000000..e4c7e34 --- /dev/null +++ b/app/views/blocks/categories.rhtml @@ -0,0 +1,3 @@ +<%= block_title(block.title) %> + +<%= display_category_menu block, block.selected_categories %> diff --git a/app/views/box_organizer/_categories_block.rhtml b/app/views/box_organizer/_categories_block.rhtml new file mode 100644 index 0000000..37bc49b --- /dev/null +++ b/app/views/box_organizer/_categories_block.rhtml @@ -0,0 +1,5 @@ +<%= _('Category types') %> + +<% @block.available_category_types.each do |type_label, type_value| %> +

<%= labelled_check_box(type_label, 'block[category_types][]', type_value.to_s, @block.category_types.include?(type_value.to_s)) %>

+<% end %> diff --git a/features/categories_block.feature b/features/categories_block.feature new file mode 100644 index 0000000..98ea287 --- /dev/null +++ b/features/categories_block.feature @@ -0,0 +1,86 @@ +Feature: categories_block + As an admin + I want to manage the categories block + + Background: + Given I am on the homepage + And the following product_categories + | name | display_in_menu | + | Food | true | + | Book | true | + And the following product_categories + | parent | name | display_in_menu | + | Food | Vegetarian | true | + | Food | Steak | true | + | Book | Fiction | false | + | Book | Literature | true | + And the following categories + | name | display_in_menu | + | Wood | true | + And the following regions + | name | display_in_menu | + | Bahia | true | + And the following blocks + | owner | type | + | environment | CategoriesBlock | + And I am logged in as admin + + @selenium + Scenario: List just product categories + Given I go to /admin/environment_design + And I follow "Edit" within ".categories-block" + And I check "Product" + When I press "Save" + Then I should see "Food" + And I should see "Book" + And I should not see "Vegetarian" + And I should not see "Steak" + And I should not see "Fiction" + + @selenium + Scenario: Show submenu if it exists + Given I go to /admin/environment_design + And I follow "Edit" within ".categories-block" + And I check "Product" + And I press "Save" + Then I should see "Food" + And I should see "Book" + And I should not see "Vegetarian" + And I should not see "Steak" + And I should not see "Literature" + When I click ".category-link-expand category-root" + Then I should see "Literature" + When I click ".category-link-expand category-root" + Then I should see "Vegetarian" + And I should see "Steak" + And I should not see "Fiction" + + @selenium + Scenario: Show only one submenu per time + Given I go to /admin/environment_design + And I follow "Edit" within ".categories-block" + And I check "Product" + And I press "Save" + Then I should see "Food" + And I should not see "Vegetarian" + And I should not see "Steak" + When I click ".category-link-expand category-root" + Then I should see "Vegetarian" + And I should see "Steak" + + @selenium + Scenario: List just general categories + Given I go to /admin/environment_design + And I follow "Edit" within ".categories-block" + And I check "Generic Category" + When I press "Save" + Then I should see "Wood" + + @selenium + Scenario: List just regions + Given I go to /admin/environment_design + And I follow "Edit" within ".categories-block" + And I check "Region" + When I press "Save" + Then I should see "Bahia" + diff --git a/features/step_definitions/noosfero_steps.rb b/features/step_definitions/noosfero_steps.rb index 1a28815..f1ec99d 100644 --- a/features/step_definitions/noosfero_steps.rb +++ b/features/step_definitions/noosfero_steps.rb @@ -21,7 +21,12 @@ end Given /^the following blocks$/ do |table| table.hashes.map{|item| item.dup}.each do |item| klass = item.delete('type') - owner = Profile[item.delete('owner')] + owner_type = item.delete('owner') + owner = owner_type == 'environment' ? Environment.default : Profile[owner_type] + if owner.boxes.empty? + owner.boxes<< Box.new + owner.boxes.first.blocks << MainBlock.new + end box_id = owner.boxes.last.id klass.constantize.create!(item.merge(:box_id => box_id)) end diff --git a/public/javascripts/application.js b/public/javascripts/application.js index dadbf7a..afb769c 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -193,3 +193,24 @@ function render_jquery_ui_buttons(element_id) { }) } } + +function expandCategory(block, id) { + var link = jQuery('#block_' + block + '_category_' + id); + if (category_expanded['block'] > 0 && category_expanded['category'] > 0 && category_expanded['block'] == block && category_expanded['category'] != id && link.hasClass('category-root')) { + expandCategory(category_expanded['block'], category_expanded['category']); + category_expanded['category'] = id; + category_expanded['block'] = block; + } + if (category_expanded['block'] == 0) category_expanded['block'] = block; + if (category_expanded['category'] == 0) category_expanded['category'] = id; + jQuery('#block_' + block + '_category_content_' + id).slideToggle('slow'); + link.toggleClass('category-expanded'); + if (link.hasClass('category-expanded')) link.html(expanded_icon); + else { + link.html(collapsed_icon); + if (link.hasClass('category-root')) { + category_expanded['block'] = 0; + category_expanded['category'] = 0; + } + } +} diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 6fad7ee..78b12ce 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -4051,3 +4051,35 @@ h1#agenda-title { .treeitem .button{ display: inline; } + +/* Categories block stuff */ + +.categories-block ul { + margin: 0; + padding: 0; + float: left; +} +.categories-block li { + display: block; + float: left; + width: 100%; +} +.categories-block ul a { + text-decoration: none; + background-color: transparent; + border-bottom: 1px solid #fff; + border-color: #fff; + line-height: 22px; +} +.categories-block div { + clear: both; +} +.categories-block p { + clear: both; +} +.categories-block li a:hover { + text-decoration: none; +} +.categories-block .ui-icon { + margin-top: 2px; +} diff --git a/test/functional/environment_design_controller_test.rb b/test/functional/environment_design_controller_test.rb index b2bf7b2..31c80cf 100644 --- a/test/functional/environment_design_controller_test.rb +++ b/test/functional/environment_design_controller_test.rb @@ -6,7 +6,7 @@ class EnvironmentDesignController; def rescue_action(e) raise e end; end class EnvironmentDesignControllerTest < Test::Unit::TestCase - ALL_BLOCKS = [ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock ] + ALL_BLOCKS = [ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, PeopleBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock ] def setup @controller = EnvironmentDesignController.new diff --git a/test/unit/categories_block_test.rb b/test/unit/categories_block_test.rb new file mode 100644 index 0000000..918eca1 --- /dev/null +++ b/test/unit/categories_block_test.rb @@ -0,0 +1,42 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class CategoriesBlockTest < ActiveSupport::TestCase + + should 'default describe' do + assert_not_equal Block.description, CategoriesBlock.description + end + + should 'default title' do + block = Block.new + category_block = CategoriesBlock.new + assert_not_equal block.title, category_block.default_title + end + + should 'have a help defined' do + category_block = CategoriesBlock.new + assert_not_nil category_block.help + end + + should 'display category block' do + block = CategoriesBlock.new + + self.expects(:render).with(:file => 'blocks/categories', :locals => { :block => block}) + instance_eval(& block.content) + end + + should 'be editable' do + assert CategoriesBlock.new.editable? + end + + should 'default category types is an empty array' do + category_block = CategoriesBlock.new + assert_kind_of Array, category_block.category_types + assert category_block.category_types.empty? + end + + should 'available category types' do + category_block = CategoriesBlock.new + assert_equal({ _('Generic category') => nil, _('Region') => 'Region', _('Product') => 'ProductCategory' }, category_block.available_category_types) + end + +end diff --git a/test/unit/category_test.rb b/test/unit/category_test.rb index 4b29c5a..cacf305 100644 --- a/test/unit/category_test.rb +++ b/test/unit/category_test.rb @@ -389,4 +389,50 @@ class CategoryTest < Test::Unit::TestCase assert Category.new.accept_products? end + should 'get categories by type including nil' do + category = Category.create!(:name => 'test category', :environment => Environment.default) + region = Region.create!(:name => 'test region', :environment => Environment.default) + product = ProductCategory.create!(:name => 'test product', :environment => Environment.default) + result = Category.from_types(['ProductCategory', '']).all + assert_equal 2, result.size + assert result.include?(product) + assert result.include?(category) + end + + should 'get categories by type and not nil' do + category = Category.create!(:name => 'test category', :environment => Environment.default) + region = Region.create!(:name => 'test region', :environment => Environment.default) + product = ProductCategory.create!(:name => 'test product', :environment => Environment.default) + result = Category.from_types(['Region', 'ProductCategory']).all + assert_equal 2, result.size + assert result.include?(region) + assert result.include?(product) + end + + should 'define a leaf to be displayed in menu' do + c1 = fast_create(Category, :display_in_menu => true) + c11 = fast_create(Category, :display_in_menu => true, :parent_id => c1.id) + c2 = fast_create(Category, :display_in_menu => true) + c21 = fast_create(Category, :display_in_menu => false, :parent_id => c2.id) + c22 = fast_create(Category, :display_in_menu => false, :parent_id => c2.id) + + assert_equal false, c1.is_leaf_displayable_in_menu? + assert_equal true, c11.is_leaf_displayable_in_menu? + assert_equal true, c2.is_leaf_displayable_in_menu? + assert_equal false, c21.is_leaf_displayable_in_menu? + assert_equal false, c22.is_leaf_displayable_in_menu? + end + + should 'filter top_level categories by type' do + toplevel_productcategory = fast_create(ProductCategory) + leaf_productcategory = fast_create(ProductCategory, :parent_id => toplevel_productcategory.id) + + toplevel_category = fast_create(Category) + leaf_category = fast_create(Category, :parent_id => toplevel_category.id) + + assert_includes Category.top_level_for(Environment.default).from_types(['ProductCategory']), toplevel_productcategory + assert_not_includes Category.top_level_for(Environment.default).from_types(['ProductCategory']), leaf_productcategory + assert_not_includes Category.top_level_for(Environment.default).from_types(['ProductCategory']), toplevel_category + end + end -- libgit2 0.21.2