Commit 90efe3ca9da72beb3c46a6e347253a5bedd94c2a

Authored by Daniela Feitosa
Committed by Joenio Costa
1 parent 2836c18c

Enhancing edition of product

Fixing categories selection and fixing width of sideboxes in leftbar and rightbar templates
Added themes to jquery-ui

(ActionItem1392)
Showing 88 changed files with 2905 additions and 196 deletions   Show diff stats
app/controllers/my_profile/manage_products_controller.rb
1 1 class ManageProductsController < ApplicationController
2 2 needs_profile
3 3  
4   - protect 'manage_products', :profile
  4 + protect 'manage_products', :profile, :except => [:show]
5 5 before_filter :check_environment_feature
  6 + before_filter :login_required, :except => [:show]
6 7  
7 8 protected
  9 +
8 10 def check_environment_feature
9 11 if profile.environment.enabled?('disable_products_for_enterprises')
10 12 render_not_found
... ... @@ -13,6 +15,7 @@ class ManageProductsController &lt; ApplicationController
13 15 end
14 16  
15 17 public
  18 +
16 19 def index
17 20 @products = @profile.products
18 21 @consumptions = @profile.consumptions
... ... @@ -51,15 +54,34 @@ class ManageProductsController &lt; ApplicationController
51 54 end
52 55  
53 56 def edit
54   - @object = @product = @profile.products.find(params[:id])
55   - @current_category = @product.product_category
56   - @categories = @current_category.nil? ? [] : @current_category.children
  57 + @product = @profile.products.find(params[:id])
  58 + field = params[:field]
57 59 if request.post?
58   - if @product.update_attributes(params[:product])
59   - flash[:notice] = _('Product succesfully updated')
60   - redirect_back_or_default :action => 'show', :id => @product
61   - else
  60 + begin
  61 + @product.update_attributes!(params[:product])
  62 + render :partial => "display_#{field}", :locals => {:product => @product}
  63 + rescue Exception => e
  64 + render :partial => "edit_#{field}", :locals => {:product => @product, :errors => true}
  65 + end
  66 + else
  67 + render :partial => "edit_#{field}", :locals => {:product => @product, :errors => false}
  68 + end
  69 + end
  70 +
  71 + def edit_category
  72 + @product = @profile.products.find(params[:id])
  73 + @category = @product.product_category
  74 + @categories = ProductCategory.top_level_for(environment)
  75 + @edit = true
  76 + @level = @category.level
  77 + if request.post?
  78 + begin
  79 + @product.update_attributes!(params[:product])
  80 + render :partial => 'shared/redirect_via_javascript',
  81 + :locals => { :url => url_for(:controller => 'manage_products', :action => 'show', :id => @product) }
  82 + rescue Exception => e
62 83 flash[:notice] = _('Could not update the product')
  84 + render :partial => 'shared/dialog_error_messages', :locals => { :object_name => 'product' }
63 85 end
64 86 end
65 87 end
... ...
app/controllers/public/catalog_controller.rb
1 1 class CatalogController < PublicController
2 2 needs_profile
  3 +
3 4 before_filter :check_enterprise_and_environment
4 5  
5 6 def index
6 7 @products = @profile.products
7 8 end
8 9  
9   - def show
10   - @product = @profile.products.find(params[:id])
11   - end
12   -
13 10 protected
14 11 def check_enterprise_and_environment
15 12 unless @profile.kind_of?(Enterprise) && !@profile.environment.enabled?('disable_products_for_enterprises')
... ...
app/helpers/application_helper.rb
... ... @@ -907,7 +907,7 @@ module ApplicationHelper
907 907 (@section ? @section.title + ' - ' : '') +
908 908 (@toc ? _('Online Manual') + ' - ' : '') +
909 909 environment.name +
910   - (@category ? "&rarr; #{@category.full_name}" : '')
  910 + (@category ? " - #{@category.full_name}" : '')
911 911 end
912 912  
913 913 def noosfero_javascript
... ... @@ -921,6 +921,7 @@ module ApplicationHelper
921 921 'lightbox',
922 922 'colorpicker',
923 923 pngfix_stylesheet_path,
  924 + 'jquery.ui/sunny/jquery-ui-1.8.2.custom',
924 925 ]
925 926 end
926 927  
... ... @@ -960,13 +961,16 @@ module ApplicationHelper
960 961 text_field_tag(name, value, options.merge(:class => 'colorpicker_field'))
961 962 end
962 963  
963   - # for now force currency to Brazillian format, like: "12.345,20"
964   - def float_to_currency(price)
965   - number_to_currency(price, :unit => 'R$', :separator => ',', :delimiter => '.')
966   - end
967   -
968 964 def ui_icon(icon_type)
969 965 "<span class='ui-icon ui-icon-#{icon_type}' style='float:left; margin-right:7px;'></span>"
970 966 end
971 967  
  968 + def ui_button(label, url, html_options = {})
  969 + link_to(label, url, html_options.merge(:class => 'ui_button fg-button'))
  970 + end
  971 +
  972 + def ui_button_to_remote(label, options, html_options = {})
  973 + link_to_remote(label, options, html_options.merge(:class => 'ui_button fg-button'))
  974 + end
  975 +
972 976 end
... ...
app/helpers/catalog_helper.rb
1 1 module CatalogHelper
2 2  
3 3 include DisplayHelper
  4 +include ManageProductsHelper
4 5  
5 6 def display_products_list(profile, products)
6 7 data = ''
... ... @@ -19,7 +20,7 @@ include DisplayHelper
19 20 content_tag('h1', _('Products/Services')) + content_tag('ul', data, :id => 'product_list')
20 21 end
21 22  
22   -private
  23 + private
23 24  
24 25 def product_category_name(profile, product_category)
25 26 if profile.enabled?
... ...
app/helpers/display_helper.rb
... ... @@ -2,7 +2,7 @@ module DisplayHelper
2 2  
3 3 def link_to_product(product, opts={})
4 4 return _('No product') unless product
5   - target = product.enterprise.enabled? ? product.enterprise.public_profile_url.merge(:controller => 'catalog', :action => 'show', :id => product) : product.enterprise.url
  5 + target = product.enterprise.enabled? ? product.enterprise.public_profile_url.merge(:controller => 'manage_products', :action => 'show', :id => product) : product.enterprise.url
6 6 link_to content_tag( 'span', product.name ),
7 7 target,
8 8 opts
... ...
app/helpers/manage_products_helper.rb
... ... @@ -49,12 +49,52 @@ module ManageProductsHelper
49 49 hierarchy.reverse.join(options[:separator] || ' &rarr; ')
50 50 end
51 51  
52   - def options_for_select_categories(categories)
  52 + def options_for_select_categories(categories, selected = nil)
53 53 categories.sort_by{|cat| cat.name.transliterate}.map do |category|
54   - "<option value='#{category.id}'>#{category.name + (category.leaf? ? '': ' &raquo;')}</option>"
  54 + selected_attribute = selected.nil? ? '' : (category == selected ? "selected='selected'" : '')
  55 + "<option value='#{category.id}' #{selected_attribute}>#{category.name + (category.leaf? ? '': ' &raquo;')}</option>"
55 56 end.join("\n")
56 57 end
57 58  
  59 + def build_selects_for_ancestors(ancestors, last_level)
  60 + current_category = ancestors.shift
  61 + if current_category.nil?
  62 + content_tag('div', '<!-- no categories -->',
  63 + :class => 'categories_container',
  64 + :id => "categories_container_level#{ last_level }"
  65 + )
  66 + else
  67 + content_tag('div',
  68 + select_tag('category_id',
  69 + options_for_select_categories(current_category.siblings + [current_category], current_category),
  70 + :size => 10,
  71 + :onchange => remote_function_to_update_categories_selection("categories_container_level#{ current_category.level + 1 }", :with => "'category_id=' + this.value")
  72 + ) +
  73 + build_selects_for_ancestors(ancestors, last_level),
  74 + :class => 'categories_container',
  75 + :id => "categories_container_level#{ current_category.level }"
  76 + )
  77 + end
  78 + end
  79 +
  80 + def selects_for_all_ancestors(current_category)
  81 + build_selects_for_ancestors(current_category.ancestors.reverse + [current_category], current_category.level + 1)
  82 + end
  83 +
  84 + def select_for_new_category
  85 + content_tag('div',
  86 + render(:partial => 'categories_for_selection'),
  87 + :class => 'categories_container',
  88 + :id => 'categories_container_level0'
  89 + )
  90 + end
  91 +
  92 + def categories_container(field_id_html, categories_selection_html, hierarchy_html = '')
  93 + field_id_html +
  94 + content_tag('div', hierarchy_html, :id => 'hierarchy_navigation') +
  95 + content_tag('div', categories_selection_html, :id => 'categories_container_wrapper')
  96 + end
  97 +
58 98 def select_for_categories(categories, level = 0)
59 99 if categories.empty?
60 100 content_tag('div', '', :id => 'no_subcategories')
... ... @@ -68,4 +108,98 @@ module ManageProductsHelper
68 108 end
69 109 end
70 110  
  111 + def edit_product_link(product, field, label, html_options = {})
  112 + return '' unless (user && user.has_permission?('manage_products', profile))
  113 + options = html_options.merge(:id => 'link-edit-product-' + field)
  114 +
  115 + link_to_remote(label,
  116 + {:update => "product-#{field}",
  117 + :url => { :controller => 'manage_products', :action => "edit", :id => product.id, :field => field },
  118 + :method => :get},
  119 + options)
  120 + end
  121 +
  122 + def edit_product_button(product, field, label, html_options = {})
  123 + the_class = 'button with-text icon-edit'
  124 + if html_options.has_key?(:class)
  125 + the_class << ' ' << html_options[:class]
  126 + end
  127 + edit_product_link(product, field, label, html_options.merge(:class => the_class))
  128 + end
  129 +
  130 + def edit_product_ui_button(product, field, label, html_options = {})
  131 + return '' unless (user && user.has_permission?('manage_products', profile))
  132 + options = html_options.merge(:id => 'edit-product-button-ui-' + field)
  133 +
  134 + ui_button_to_remote(label,
  135 + {:update => "product-#{field}",
  136 + :url => { :controller => 'manage_products', :action => "edit", :id => product.id, :field => field },
  137 + :complete => "$('edit-product-button-ui-#{field}').hide()",
  138 + :method => :get},
  139 + options)
  140 + end
  141 +
  142 + def cancel_edit_product_link(product, field, html_options = {})
  143 + return '' unless (user && user.has_permission?('manage_products', profile))
  144 + button_to_function(:cancel, _('Cancel'), nil, html_options) do |page|
  145 + page.replace_html "product-#{field}", :partial => "display_#{field}", :locals => {:product => product}
  146 + end
  147 + end
  148 +
  149 + def edit_product_category_link(product, html_options = {})
  150 + return '' unless (user && user.has_permission?('manage_products', profile))
  151 + options = html_options.merge(:id => 'link-edit-product-category')
  152 + link_to(_('Change category'), { :action => 'edit_category', :id => product.id}, options)
  153 + end
  154 +
  155 + def float_to_currency(value)
  156 + number_to_currency(value, :unit => environment.currency_unit, :separator => environment.currency_separator, :delimiter => environment.currency_delimiter, :format => "%u %n")
  157 + end
  158 +
  159 + def display_value(product)
  160 + price = product.price
  161 + unless price.blank? || price.zero?
  162 + unit = product.unit
  163 + return '' if unit.blank?
  164 + discount = product.discount
  165 + if discount.blank? || discount.zero?
  166 + display_price(_('Price: '), price, unit)
  167 + else
  168 + display_price_with_discount(price, unit, product.price_with_discount)
  169 + end
  170 + else
  171 + ''
  172 + end
  173 + end
  174 +
  175 + def display_price(label, price, unit)
  176 + content_tag('span', label, :class => 'field-name') +
  177 + content_tag('span', float_to_currency(price), :class => 'field-value') +
  178 + ' (%s)' % unit
  179 + end
  180 +
  181 + def display_price_with_discount(price, unit, price_with_discount)
  182 + original_value = content_tag('span', float_to_currency(price), :class => 'field-value')
  183 + discount_value = display_price(_('On sale: '), price_with_discount, unit)
  184 + content_tag('span', _('List price: '), :class => 'field-name') + original_value + "<p/>" + discount_value
  185 + end
  186 +
  187 + def display_qualifiers(product)
  188 + data = ''
  189 + product.product_qualifiers.each do |pq|
  190 + certified_by = pq.certifier ? _(' certified by %s') % link_to(pq.certifier.name, pq.certifier.link) : ''
  191 + data << content_tag('li', '✔ ' + pq.qualifier.name + certified_by, :class => 'product-qualifiers-item')
  192 + end
  193 + content_tag('ul', data, :id => 'product-qualifiers')
  194 + end
  195 +
  196 + def checkboxes_qualifiers(product, qualifier)
  197 + check_box_tag("product[qualifiers_list][#{qualifier.id}][qualifier_id]", qualifier.id, product.qualifiers.include?(qualifier)) + qualifier.name
  198 + end
  199 +
  200 + def select_certifiers(product, qualifier, certifiers)
  201 + relation = product.product_qualifiers.find(:first, :conditions => {:qualifier_id => qualifier.id})
  202 + selected = relation.nil? ? 0 : relation.certifier_id
  203 + select_tag("product[qualifiers_list][#{qualifier.id}][certifier_id]", options_for_select([[_('Select...') , 0 ]] + certifiers.map {|c|[c.name, c.id]}, selected))
  204 + end
71 205 end
... ...
app/models/certifier.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +class Certifier < ActiveRecord::Base
  2 +
  3 + belongs_to :environment
  4 +
  5 + def link
  6 + self[:link] || ''
  7 + end
  8 +end
... ...
app/models/environment.rb
... ... @@ -152,6 +152,9 @@ class Environment &lt; ActiveRecord::Base
152 152  
153 153 has_many :roles
154 154  
  155 + has_many :qualifiers
  156 + has_many :certifiers
  157 +
155 158 acts_as_accessible
156 159  
157 160 def superior_intances
... ... @@ -209,6 +212,10 @@ class Environment &lt; ActiveRecord::Base
209 212 settings_items :help_message_to_add_enterprise, :type => String, :default => ''
210 213 settings_items :tip_message_enterprise_activation_question, :type => String, :default => ''
211 214  
  215 + settings_items :currency_unit, :type => String, :default => '$'
  216 + settings_items :currency_separator, :type => String, :default => '.'
  217 + settings_items :currency_delimiter, :type => String, :default => ','
  218 +
212 219 def news_amount_by_folder=(amount)
213 220 settings[:news_amount_by_folder] = amount.to_i
214 221 end
... ...
app/models/product.rb
... ... @@ -2,10 +2,14 @@ class Product &lt; ActiveRecord::Base
2 2 belongs_to :enterprise
3 3 belongs_to :product_category
4 4 has_many :product_categorizations
  5 + has_many :product_qualifiers
  6 + has_many :qualifiers, :through => :product_qualifiers
5 7  
6 8 validates_uniqueness_of :name, :scope => :enterprise_id, :allow_nil => true
7 9 validates_presence_of :product_category
  10 +
8 11 validates_numericality_of :price, :allow_nil => true
  12 + validates_numericality_of :discount, :allow_nil => true
9 13  
10 14 after_update :save_image
11 15  
... ... @@ -31,10 +35,36 @@ class Product &lt; ActiveRecord::Base
31 35  
32 36 acts_as_searchable :fields => [ :name, :description, :category_full_name ]
33 37  
34   - xss_terminate :only => [ :name, :description ], :on => 'validation'
  38 + xss_terminate :only => [ :name ], :on => 'validation'
  39 + xss_terminate :only => [ :description ], :with => 'white_list', :on => 'validation'
35 40  
36 41 acts_as_mappable
37   -
  42 +
  43 + def self.units
  44 + {
  45 + _('Litre') => 'litre',
  46 + _('Kilo') => 'kilo',
  47 + _('Meter') => 'meter',
  48 + _('Unit') => 'unit',
  49 + }
  50 + end
  51 +
  52 + def name
  53 + self[:name].blank? ? category_name : self[:name]
  54 + end
  55 +
  56 + def name=(value)
  57 + if (value == category_name)
  58 + self[:name] = nil
  59 + else
  60 + self[:name] = value
  61 + end
  62 + end
  63 +
  64 + def default_image(size='thumb')
  65 + '/images/icons-app/product-default-pic-%s.png' % size
  66 + end
  67 +
38 68 def category_full_name
39 69 product_category ? product_category.full_name.split('/') : nil
40 70 end
... ... @@ -60,13 +90,21 @@ class Product &lt; ActiveRecord::Base
60 90 end
61 91  
62 92 def url
63   - enterprise.public_profile_url.merge(:controller => 'catalog', :action => 'show', :id => id)
  93 + enterprise.public_profile_url.merge(:controller => 'manage_products', :action => 'show', :id => id)
64 94 end
65 95  
66 96 def public?
67 97 enterprise.public_profile
68 98 end
69 99  
  100 + def formatted_value(value)
  101 + ("%.2f" % self[value]).to_s.gsub('.', enterprise.environment.currency_separator) if self[value]
  102 + end
  103 +
  104 + def price_with_discount
  105 + price - discount if discount
  106 + end
  107 +
70 108 def price=(value)
71 109 if value.is_a?(String)
72 110 super(currency_to_float(value))
... ... @@ -75,6 +113,14 @@ class Product &lt; ActiveRecord::Base
75 113 end
76 114 end
77 115  
  116 + def discount=(value)
  117 + if value.is_a?(String)
  118 + super(currency_to_float(value))
  119 + else
  120 + super(value)
  121 + end
  122 + end
  123 +
78 124 def currency_to_float( num )
79 125 if num.count('.') == 1 && num.count(',') == 0
80 126 # number like "12.34"
... ... @@ -99,4 +145,17 @@ class Product &lt; ActiveRecord::Base
99 145 return num.tr(',.','').to_f
100 146 end
101 147  
  148 + def has_basic_info?
  149 + %w[description price].each do |field|
  150 + return true if !self.send(field).blank?
  151 + end
  152 + false
  153 + end
  154 +
  155 + def qualifiers_list=(qualifiers)
  156 + self.product_qualifiers.delete_all
  157 + qualifiers.each do |item|
  158 + self.product_qualifiers.create(item[1]) if item[1].has_key?('qualifier_id')
  159 + end
  160 + end
102 161 end
... ...
app/models/product_qualifier.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class ProductQualifier < ActiveRecord::Base
  2 + belongs_to :qualifier
  3 + belongs_to :product
  4 + belongs_to :certifier
  5 +end
... ...
app/models/products_block.rb
... ... @@ -20,7 +20,7 @@ class ProductsBlock &lt; Block
20 20 block_title(title) +
21 21 content_tag(
22 22 'ul',
23   - products.map {|product| content_tag('li', link_to(product.name, product.url, :style => 'background-image:url(%s)' % ( product.image ? product.image.public_filename(:minor) : '/images/icons-app/product-default-pic-minor.png' )), :class => 'product' )}
  23 + products.map {|product| content_tag('li', link_to(product.name, product.url, :style => 'background-image:url(%s)' % ( product.image ? product.image.public_filename(:minor) : product.default_image('minor'))), :class => 'product' )}
24 24 )
25 25 end
26 26  
... ...
app/models/profile.rb
... ... @@ -386,7 +386,7 @@ class Profile &lt; ActiveRecord::Base
386 386  
387 387 def url_options
388 388 options = { :host => default_hostname, :profile => (own_hostname ? nil : self.identifier) }
389   - Noosfero.url_options.merge(options)
  389 + options.merge(Noosfero.url_options.merge(options))
390 390 end
391 391  
392 392 private :generate_url, :url_options
... ...
app/models/qualifier.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class Qualifier < ActiveRecord::Base
  2 +
  3 + belongs_to :environment
  4 +
  5 +end
... ...
app/views/catalog/show.rhtml
... ... @@ -1,25 +0,0 @@
1   -<div id="show_product">
2   -
3   -<h1> <%= @product.name %> </h1>
4   -
5   -<%= image_tag @product.image.public_filename, :class => 'product-pic' if @product.image %>
6   -
7   -<div class="product_description">
8   -<%= txt2html @product.description if @product.description %>
9   -</div>
10   -
11   -<% if @product.price %>
12   - <p class="product_price">
13   - <%= _('Price: %s') % ( "<strong>%.2f</strong>" % @product.price) %>
14   - </p>
15   -<% end %>
16   -
17   -<p class="product_category">
18   -<%= _('Category: %s ') % link_to_product_category(@product.product_category) %>
19   -</p>
20   -
21   -<% button_bar do %>
22   - <%= button :back, _("%s's products/services listing") % profile.name, :action => 'index', :id => nil %>
23   -<% end %>
24   -
25   -</div>
app/views/cms/_upload_file_form.rhtml
... ... @@ -11,7 +11,7 @@
11 11 <% end %>
12 12 </div>
13 13  
14   -<%= hidden_field_tag('back_to', @back_to) if @back_to %>
  14 +<%= hidden_field_tag('back_to', @back_to) %>
15 15  
16 16 <% button_bar do %>
17 17 <%= add_upload_file_field(_('More files'), {:size => size}) %>
... ...
app/views/layouts/_javascript.rhtml
1   -<%= javascript_include_tag :defaults, 'jquery-latest.js', 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'colorpicker', 'colorpicker-noosfero', 'reflection', :cache => 'cache-general' %>
  1 +<%= javascript_include_tag :defaults, 'jquery-latest.js', 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'colorpicker', 'colorpicker-noosfero', 'reflection', :cache => 'cache-general' %>
... ...
app/views/layouts/application.rhtml
... ... @@ -15,7 +15,6 @@
15 15 <%=
16 16 # Load the principal css files:
17 17 stylesheet_link_tag(noosfero_stylesheets, :cache => 'cache') +
18   - stylesheet_link_tag('jquery.ui/smoothness/jquery-ui-1.8.2.custom') +
19 18 stylesheet_import( %w( common help menu article button search blocks forms login-box ),
20 19 :themed_source => true ) + "\n" +
21 20 import_blocks_stylesheets(:themed_source => true) + "\n" +
... ... @@ -126,5 +125,10 @@
126 125 app/views/shared/noosfero_layout_features.rhtml! %>
127 126 <%= noosfero_layout_features %>
128 127  
  128 + <script type='text/javascript'>
  129 + // transform all element with class ui_button in a jQuery UI button
  130 + render_jquery_ui_buttons()
  131 + </script>
  132 +
129 133 </body>
130 134 </html>
... ...
app/views/manage_products/_display_category.rhtml 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +<div id='display-product-category'>
  2 + <p><%= hierarchy_category_navigation(@product.product_category, :make_links => false)%></p>
  3 + <%= edit_product_category_link(@product) %>
  4 +</div>
... ...
app/views/manage_products/_display_image.rhtml 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +<div id='display-product-image'>
  2 + <%= image_tag (@product.reload.image ? @product.image.public_filename : @product.default_image('thumb')), :class => 'product-pic' %>
  3 +</div>
  4 +
  5 +<%= edit_product_link(@product, 'image', _('Change image')) %>
... ...
app/views/manage_products/_display_info.rhtml 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +<%= render :file => 'shared/tiny_mce', :locals => {:mode => 'simple'} %>
  2 +<div id='display-product-info'>
  3 + <%= display_value(@product) %>
  4 + <%= display_qualifiers(@product) %>
  5 +</div>
  6 +
  7 +<div id='display-product-description'><%= @product.description %></div>
  8 +
  9 +<% if @product.has_basic_info? %>
  10 + <%= edit_product_button(@product, 'info', _('Edit basic information')) %>
  11 +<% else %>
  12 + <%= edit_product_ui_button(@product, 'info', _('Add description, price and other basic information'), :title => _('Click here to add description, price, discount and qualifiers/certifiers to make your product more attractive and detailed for the consumers'), 'data-primary-icon' => 'ui-icon-pencil', 'data-secondary-icon' => 'ui-icon-triangle-1-s') %>
  13 + <%= javascript_tag("render_jquery_ui_buttons('edit-product-button-ui-info')") %>
  14 +<% end%>
... ...
app/views/manage_products/_display_name.rhtml 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +<div id='display-product-name'>
  2 + <h2><%= @product.reload.name %></h2>
  3 + <%= edit_product_link(@product, 'name', _('Edit name')) %>
  4 +</div>
  5 +
... ...
app/views/manage_products/_edit_image.rhtml 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +<div id='display-product-image'>
  2 + <%= image_tag (@product.reload.image ? @product.image.public_filename : @product.default_image('thumb')), :class => 'product-pic' %>
  3 +</div>
  4 +
  5 +<% form_for(:product, :url => { :controller => 'manage_products', :action => 'edit', :id => @product, :field => 'image' }, :html => { :method => 'post', :id => 'uploadForm', :multipart => true}) do |f| %>
  6 + <% f.fields_for :image_builder, @product.image do |i| %>
  7 + <%= i.file_field( :uploaded_data, { :size => 10 } ) %>
  8 + <p><%= _("Max size: %s (.jpg, .gif, .png)")% Image.max_size.to_humanreadable %></p>
  9 + <% end %>
  10 +
  11 + <%= submit_button 'save', _('Save') %>
  12 + <%= cancel_edit_product_link(@product, 'image') %>
  13 +<% end %>
  14 +
  15 +<script type="text/javascript">
  16 + jQuery("#uploadForm").ajaxForm({
  17 + beforeSubmit: function(a,f,o) {
  18 + o.loading = open_loading('<%= _('loading... ') %>');
  19 + o.loaded = close_loading();
  20 + },
  21 + target: '#product-image'
  22 + });
  23 +</script>
  24 +
  25 +<% if errors %>
  26 + <%= render :partial => 'shared/dialog_error_messages', :locals => { :object_name => 'product' } %>
  27 +<% end %>
... ...
app/views/manage_products/_edit_info.rhtml 0 → 100644
... ... @@ -0,0 +1,49 @@
  1 +<% if errors %>
  2 + <%= render :partial => 'shared/dialog_error_messages', :locals => { :object_name => 'product' } %>
  3 +<% end %>
  4 +
  5 +<%= render :file => 'shared/tiny_mce', :locals => {:mode => 'simple'} %>
  6 +<% remote_form_for(@product,
  7 + :loading => "open_loading('#{ _('loading...') }')",
  8 + :loaded => 'close_loading()',
  9 + :before => ("tinyMCE.triggerSave()" unless Rails.env == 'test'),
  10 + :update => 'product-info',
  11 + :url => {:controller => 'manage_products', :action => 'edit', :id => @product, :field => 'info'},
  12 + :html => {:id => 'product-info-form', :method => 'post'}) do |f| %>
  13 + <%= labelled_form_field(_('Description:'), f.text_area(:description, :rows => 15, :style => 'width: 90%;')) %>
  14 + <%= labelled_form_field(_('Price (%s):') % environment.currency_unit, f.text_field(:price, :value => @product.formatted_value(:price), :onKeyPress => "return numbersonly(event, '#{environment.currency_separator}');")) %>
  15 + <%= labelled_form_field(_('Unit:'), f.select(:unit, Product.units)) %>
  16 + <%= labelled_form_field(_('Discount (%s):') % environment.currency_unit, f.text_field(:discount, :value => @product.formatted_value(:discount), :onKeyPress => "return numbersonly(event, '#{environment.currency_separator}');")) %>
  17 + <div class='formlabel'><%= _('Available:') %></div>
  18 + <div class='formfield'>
  19 + <%= labelled_radio_button( _('Yes'), 'product[available]', 'true', @product.available) %>
  20 + <%= labelled_radio_button( _('No'), 'product[available]', 'false', !@product.available) %>
  21 + </div>
  22 +
  23 +
  24 + <% if !environment.qualifiers.empty? %>
  25 + <table id='product-qualifiers-list'>
  26 + <tr>
  27 + <th><%= _('Qualifier') %></th>
  28 + <th><%= _('Certifier') %></th>
  29 + </tr>
  30 + <% environment.qualifiers.each do |qualifier| %>
  31 + <tr>
  32 + <td>
  33 + <%= checkboxes_qualifiers(@product, qualifier) %>
  34 + </td>
  35 + <td>
  36 + <%= select_certifiers(@product, qualifier, environment.certifiers) %>
  37 + </td>
  38 + </tr>
  39 + <% end %>
  40 + </table>
  41 + <% end %>
  42 +
  43 + <% button_bar do %>
  44 + <%= submit_button :save, _('Save') %>
  45 + <%= cancel_edit_product_link(@product, 'info') %>
  46 + <% end %>
  47 +<% end %>
  48 +
  49 +<br style='clear:both'>
... ...
app/views/manage_products/_edit_name.rhtml 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +<% remote_form_for(@product,
  2 + :loading => "open_loading('#{ _('loading...') }')",
  3 + :loaded => 'close_loading()',
  4 + :update => 'product-name',
  5 + :url => {:controller => 'manage_products', :action => 'edit', :id => @product, :field => 'name'},
  6 + :html => {:method => 'post'}) do |f| %>
  7 + <%= f.text_field(:name, :value => @product.name, :class => 'name_edition') %>
  8 +
  9 + <% button_bar do %>
  10 + <%= submit_button :save, _('Save') %>
  11 + <%= cancel_edit_product_link(@product, 'name') %>
  12 + <% end %>
  13 +<% end %>
  14 +
  15 +<script type="text/javascript">
  16 + jQuery('#product_name').focus().select();
  17 +</script>
  18 +
  19 +<% if errors %>
  20 + <%= render :partial => 'shared/dialog_error_messages', :locals => { :object_name => 'product' } %>
  21 +<% end %>
... ...
app/views/manage_products/edit_category.rhtml 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +<div class=product-category-hierarchy>
  2 + <%= hierarchy_category_navigation(@product.product_category, :make_links => false)%>
  3 +</div>
  4 +
  5 +<div id="category-product-edition">
  6 +
  7 + <h2><%= @product.name %></h2>
  8 +
  9 + <div id='request_result_message' style='display: none'></div>
  10 +
  11 + <% remote_form_for(:product, @product,
  12 + :loading => "open_loading('#{ _('loading...') }')",
  13 + :loaded => "close_loading()",
  14 + :update => "request_result_message",
  15 + :url => {:action => 'edit_category', :id => @product, :field => 'category'},
  16 + :html => {:method => 'post', :id => 'product_category_form'}) do |f| %>
  17 +
  18 + <h3><%= _('Edit category of this product:') %></h3>
  19 +
  20 + <%= categories_container(f.hidden_field('product_category_id'), selects_for_all_ancestors(@category), hierarchy_category_navigation(@category, :make_links => true)) %>
  21 +
  22 + <div id='categories_selection_actionbar'>
  23 + <%= button(:back, _('Back to product'), :action => 'show', :id => @product) %>
  24 + <span id='save_and_continue_wrapper'>
  25 + <%= submit_button(:save, _('Save and continue'), :id => 'save_and_continue') %>
  26 + <span class='tooltip' id='save_and_continue_disabled_tooltip'>
  27 + <%= ui_icon(:alert) %>
  28 + <%= _('This category does not allow registration of products, select a more specific category') %>
  29 + </span>
  30 + </span>
  31 + </div>
  32 +
  33 + <% end %>
  34 +</div>
... ...
app/views/manage_products/index.rhtml
... ... @@ -16,7 +16,6 @@
16 16 <td><strong><%= link_to product.name, :action => 'show', :id => product %></strong></td>
17 17 <td><%= product.price %></td>
18 18 <td>
19   - <%= button :edit, _('Edit'), :action => 'edit', :id => product %>
20 19 <%= button :delete, _('Destroy'), :action => 'destroy', :id => product %>
21 20 </td>
22 21 </tr>
... ...
app/views/manage_products/new.rhtml
... ... @@ -6,19 +6,11 @@
6 6 :loading => "open_loading('#{ _('loading...') }')",
7 7 :update => "request_result_message",
8 8 :url => {:action => 'new'},
9   - :html => {:method => 'post', :id => 'new_product_form'} do |f| %>
  9 + :html => {:method => 'post', :id => 'product_category_form'} do |f| %>
10 10  
11 11 <h3><%= _('Select the category of the new product or service') %></h3>
12 12  
13   - <%= f.hidden_field 'product_category_id' %>
14   -
15   - <div id='hierarchy_navigation'> </div>
16   -
17   - <div id='categories_container_wrapper'>
18   - <div class='categories_container' id='categories_container_level0'>
19   - <%= render :partial => 'categories_for_selection' %>
20   - </div>
21   - </div>
  13 + <%= categories_container(f.hidden_field('product_category_id'), select_for_new_category) %>
22 14  
23 15 <div id='categories_selection_actionbar'>
24 16 <%= button :back, _('Back to the product listing'), :action => 'index' %>
... ...
app/views/manage_products/show.rhtml
1   -<h3> <%= @product.name %> </h3>
  1 +<div id="product-category">
  2 + <%= render :partial => 'manage_products/display_category', :locals => {:product => @product} %>
  3 +</div>
2 4  
3   -<p> <%= image_tag @product.image.public_filename if @product.image %> </p>
4   -<p> <strong><%= _('Price:') %></strong> <%= @product.price %> </p>
5   -<p> <strong><%= _('Description:') %></strong> <%= txt2html @product.description if @product.description %> </p>
6   -<p> <strong><%= _('Category:') %></strong> <%= link_to_product_category(@product.product_category) %> </p>
  5 +<div id="show_product">
7 6  
8   -<%= button :back, _('Back'), :action => 'index' %>
9   -&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
10   -<%= button :delete, _('Destroy'), :action => 'destroy', :id => @product %>
11   -<%= button :edit, _('Edit'), :action => 'edit', :id => @product %>
  7 + <div id='product-name'>
  8 + <%= render :partial => 'manage_products/display_name', :locals => {:product => @product} %>
  9 + </div>
12 10  
  11 + <div id='product-details'>
  12 + <div id='product-image'>
  13 + <%= render :partial => 'manage_products/display_image', :locals => {:product => @product} %>
  14 + </div>
  15 +
  16 + <div id='product-info'>
  17 + <%= render :partial => 'manage_products/display_info', :locals => {:product => @product} %>
  18 + </div>
  19 +
  20 + </div>
  21 +</div>
  22 +
  23 +<% button_bar do %>
  24 + <%= button :back, _('Back to the product listing'), :action => 'index' %>
  25 + <%= button :delete, _('Remove product or service'), {:action => 'destroy', :id => @product}, :class => 'requires-permission-manage_products', :style => 'display:none;' %>
  26 +<% end %>
... ...
app/views/shared/_dialog_error_messages.rhtml
1   -<%= error_messages_for object_name %>
2   -
3 1 <% javascript_tag do %>
4 2 close_loading()
5 3 jQuery('#errorExplanation h2').hide()
... ... @@ -11,3 +9,4 @@
11 9 title: "<%= ui_icon(:alert) %>Ops…",
12 10 });
13 11 <% end %>
  12 +<%= error_messages_for object_name %>
... ...
app/views/shared/tiny_mce.rhtml
1 1 <%= javascript_include_tag 'tinymce/jscripts/tiny_mce/tiny_mce.js' %>
2 2 <script type="text/javascript">
3 3 var myplugins = "searchreplace,print,table";
  4 + var first_line, second_line;
  5 + var mode = '<%= mode ||= false %>'
  6 + <% if mode %>
  7 + first_line = "fontsizeselect,bold,italic,underline,bullist,numlist,justifyleft,justifycenter,justifyright,link,unlink"
  8 + second_line = ""
  9 + <% else %>
  10 + first_line = "print,separator,copy,paste,separator,undo,redo,separator,search,replace,separator,fontsizeselect,formatselect"
  11 + second_line = "bold,italic,underline,strikethrough,separator,bullist,numlist,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,link,unlink,image,table,separator,cleanup,code"
  12 + <% end %>
  13 +
4 14 if (tinymce.isIE) {
5 15 // the paste plugin is only useful in Internet Explorer
6 16 myplugins = "paste," + myplugins;
... ... @@ -14,8 +24,8 @@ tinyMCE.init({
14 24 plugins: myplugins,
15 25 theme_advanced_toolbar_location : "top",
16 26 theme_advanced_layout_manager: 'SimpleLayout',
17   - theme_advanced_buttons1 : "print,separator,copy,paste,separator,undo,redo,separator,search,replace,separator,fontsizeselect,formatselect",
18   - theme_advanced_buttons2 : "bold,italic,underline,strikethrough,separator,bullist,numlist,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,link,unlink,image,table,separator,cleanup,code",
  27 + theme_advanced_buttons1 : first_line,
  28 + theme_advanced_buttons2 : second_line,
19 29 theme_advanced_buttons3 : "",
20 30 theme_advanced_blockformats :"p,address,pre,h2,h3,h4,h5,h6",
21 31 paste_auto_cleanup_on_paste : true,
... ...
config/routes.rb
... ... @@ -60,7 +60,6 @@ ActionController::Routing::Routes.draw do |map|
60 60  
61 61 # catalog
62 62 map.catalog 'catalog/:profile', :controller => 'catalog', :action => 'index', :profile => /#{Noosfero.identifier_format}/
63   - map.product 'catalog/:profile/:id', :controller => 'catalog', :action => 'show', :profile => /#{Noosfero.identifier_format}/
64 63  
65 64 # invite
66 65 map.invite 'profile/:profile/invite/:action', :controller => 'invite', :profile => /#{Noosfero.identifier_format}/
... ...
db/migrate/20100618021603_add_columns_to_products.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +class AddColumnsToProducts < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :products, :unit, :string
  4 + add_column :products, :discount, :float
  5 + add_column :products, :available, :boolean
  6 + end
  7 +
  8 + def self.down
  9 + remove_column :products, :unit
  10 + remove_column :products, :discount
  11 + remove_column :products, :available
  12 + end
  13 +end
... ...
db/migrate/20100619114048_create_product_qualifiers.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +class CreateProductQualifiers < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :product_qualifiers do |t|
  4 + t.references :product
  5 + t.references :qualifier
  6 + t.references :certifier
  7 + t.timestamps
  8 + end
  9 + end
  10 +
  11 + def self.down
  12 + drop_table :product_qualifiers
  13 + end
  14 +end
... ...
db/migrate/20100619120420_create_qualifiers.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +class CreateQualifiers < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :qualifiers do |t|
  4 + t.string :name
  5 + t.references :environment
  6 +
  7 + t.timestamps
  8 + end
  9 + end
  10 +
  11 + def self.down
  12 + drop_table :qualifiers
  13 + end
  14 +end
... ...
db/migrate/20100619121512_create_certifiers.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +class CreateCertifiers < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :certifiers do |t|
  4 + t.string :name
  5 + t.string :description
  6 + t.string :link
  7 + t.references :environment
  8 +
  9 + t.timestamps
  10 + end
  11 + end
  12 +
  13 + def self.down
  14 + drop_table :certifiers
  15 + end
  16 +end
... ...
db/schema.rb
... ... @@ -141,6 +141,15 @@ ActiveRecord::Schema.define(:version =&gt; 20100621235235) do
141 141 add_index "categories_profiles", ["category_id"], :name => "index_categories_profiles_on_category_id"
142 142 add_index "categories_profiles", ["profile_id"], :name => "index_categories_profiles_on_profile_id"
143 143  
  144 + create_table "certifiers", :force => true do |t|
  145 + t.string "name"
  146 + t.string "description"
  147 + t.string "link"
  148 + t.integer "environment_id"
  149 + t.datetime "created_at"
  150 + t.datetime "updated_at"
  151 + end
  152 +
144 153 create_table "comments", :force => true do |t|
145 154 t.string "title"
146 155 t.text "body"
... ... @@ -230,6 +239,14 @@ ActiveRecord::Schema.define(:version =&gt; 20100621235235) do
230 239 add_index "product_categorizations", ["category_id"], :name => "index_product_categorizations_on_category_id"
231 240 add_index "product_categorizations", ["product_id"], :name => "index_product_categorizations_on_product_id"
232 241  
  242 + create_table "product_qualifiers", :force => true do |t|
  243 + t.integer "product_id"
  244 + t.integer "qualifier_id"
  245 + t.integer "certifier_id"
  246 + t.datetime "created_at"
  247 + t.datetime "updated_at"
  248 + end
  249 +
233 250 create_table "products", :force => true do |t|
234 251 t.integer "enterprise_id"
235 252 t.integer "product_category_id"
... ... @@ -241,6 +258,9 @@ ActiveRecord::Schema.define(:version =&gt; 20100621235235) do
241 258 t.datetime "updated_at"
242 259 t.float "lat"
243 260 t.float "lng"
  261 + t.string "unit"
  262 + t.float "discount"
  263 + t.boolean "available"
244 264 t.boolean "highlighted"
245 265 end
246 266  
... ... @@ -276,6 +296,13 @@ ActiveRecord::Schema.define(:version =&gt; 20100621235235) do
276 296  
277 297 add_index "profiles", ["environment_id"], :name => "index_profiles_on_environment_id"
278 298  
  299 + create_table "qualifiers", :force => true do |t|
  300 + t.string "name"
  301 + t.integer "environment_id"
  302 + t.datetime "created_at"
  303 + t.datetime "updated_at"
  304 + end
  305 +
279 306 create_table "refused_join_community", :id => false, :force => true do |t|
280 307 t.integer "person_id"
281 308 t.integer "community_id"
... ...
features/manage_products.feature
... ... @@ -10,18 +10,28 @@ Feature: manage products
10 10 | identifier | owner | name | enabled |
11 11 | redemoinho | joaosilva | Rede Moinho | true |
12 12 And feature "disable_products_for_enterprises" is disabled on environment
13   - And I am logged in as "joaosilva"
14   - And I am on Rede Moinho's control panel
15   - And I follow "Manage Products and Services"
16 13  
17 14 Scenario: listing products and services
  15 + Given I am logged in as "joaosilva"
  16 + And I am on Rede Moinho's control panel
  17 + And I follow "Manage Products and Services"
18 18 Then I should see "Listing products and services"
19 19  
  20 + Scenario: see button to back in categories hierarchy
  21 + Given I am logged in as "joaosilva"
  22 + And I am on Rede Moinho's control panel
  23 + And I follow "Manage Products and Services"
  24 + When I follow "New product or service"
  25 + Then I should see "Back to the product listing" link
  26 +
20 27 Scenario: see toplevel categories
21 28 Given the following product_categories
22 29 | name |
23 30 | Products |
24 31 | Services |
  32 + Given I am logged in as "joaosilva"
  33 + And I am on Rede Moinho's control panel
  34 + And I follow "Manage Products and Services"
25 35 When I follow "New product or service"
26 36 Then I should see "Products"
27 37 And I should see "Service"
... ... @@ -35,6 +45,9 @@ Feature: manage products
35 45 | name | parent |
36 46 | Computers level1 | products-level0 |
37 47 | DVDs level1 | products-level0 |
  48 + Given I am logged in as "joaosilva"
  49 + And I am on Rede Moinho's control panel
  50 + And I follow "Manage Products and Services"
38 51 When I follow "New product or service"
39 52 And I select "Products level0 »"
40 53 Then I should see "Computers level1"
... ... @@ -50,6 +63,9 @@ Feature: manage products
50 63 | name | parent |
51 64 | Computers level1 | products-level0 |
52 65 | Software development level1 | services-level0 |
  66 + Given I am logged in as "joaosilva"
  67 + And I am on Rede Moinho's control panel
  68 + And I follow "Manage Products and Services"
53 69 When I follow "New product or service"
54 70 And I select "Products level0 »"
55 71 And I select "Computers level1"
... ... @@ -65,6 +81,9 @@ Feature: manage products
65 81 And the following product_category
66 82 | name | parent |
67 83 | Computers | products |
  84 + Given I am logged in as "joaosilva"
  85 + And I am on Rede Moinho's control panel
  86 + And I follow "Manage Products and Services"
68 87 When I follow "New product or service"
69 88 And I select "Products »"
70 89 And I select "Computers"
... ... @@ -78,6 +97,9 @@ Feature: manage products
78 97 Given the following product_category
79 98 | name | parent |
80 99 | Category Level 1 | toplevel-product-categories |
  100 + Given I am logged in as "joaosilva"
  101 + And I am on Rede Moinho's control panel
  102 + And I follow "Manage Products and Services"
81 103 When I follow "New product or service"
82 104 And I select "Toplevel Product Categories »"
83 105 And I select "Category Level 1"
... ... @@ -89,6 +111,7 @@ Feature: manage products
89 111 Given the following product_category
90 112 | name |
91 113 | Only for test |
  114 + And I am logged in as "joaosilva"
92 115 When I go to /myprofile/redemoinho/manage_products/new
93 116 Then the "#save_and_continue" button should not be enabled
94 117  
... ... @@ -97,6 +120,9 @@ Feature: manage products
97 120 Given the following product_category
98 121 | name |
99 122 | Browsers (accept categories) |
  123 + Given I am logged in as "joaosilva"
  124 + And I am on Rede Moinho's control panel
  125 + And I follow "Manage Products and Services"
100 126 When I follow "New product or service"
101 127 And I select "Browsers (accept categories)"
102 128 Then the "Save and continue" button should be enabled
... ... @@ -106,6 +132,9 @@ Feature: manage products
106 132 Given the following product_category
107 133 | name | accept_products |
108 134 | Browsers | false |
  135 + Given I am logged in as "joaosilva"
  136 + And I am on Rede Moinho's control panel
  137 + And I follow "Manage Products and Services"
109 138 When I follow "New product or service"
110 139 And I select "Browsers"
111 140 Then the "#save_and_continue" button should not be enabled
... ... @@ -115,16 +144,23 @@ Feature: manage products
115 144 Given the following product_category
116 145 | name |
117 146 | Bicycle |
  147 + Given I am logged in as "joaosilva"
  148 + And I am on Rede Moinho's control panel
  149 + And I follow "Manage Products and Services"
118 150 When I follow "New product or service"
119 151 And I select "Bicycle"
120 152 And I press "Save and continue"
121   - Then I should see "Category: Bicycle"
  153 + Then I should see "Bicycle"
  154 + And I should see "Change category"
122 155  
123 156 @selenium
124 157 Scenario: stay on the same place after error on save
125 158 Given the following product_category
126 159 | name |
127 160 | Bicycle |
  161 + Given I am logged in as "joaosilva"
  162 + And I am on Rede Moinho's control panel
  163 + And I follow "Manage Products and Services"
128 164 And I follow "New product or service"
129 165 And I select "Bicycle"
130 166 And I press "Save and continue"
... ... @@ -134,3 +170,184 @@ Feature: manage products
134 170 And I press "Save and continue"
135 171 Then I should be on Rede Moinho's new product page
136 172 And I should see "Bicycle"
  173 +
  174 + Scenario: a user with permission can see edit links
  175 + Given the following product_category
  176 + | name |
  177 + | Bicycle |
  178 + And the following products
  179 + | owner | category | name | description |
  180 + | redemoinho | bicycle | Bike | Red bicycle |
  181 + And I am logged in as "joaosilva"
  182 + When I go to Rede Moinho's page of product Bike
  183 + Then I should see "Change category"
  184 + And I should see "Edit name"
  185 + And I should see "Edit basic information"
  186 + And I should see "Change image"
  187 +
  188 + Scenario: an allowed user will see a different button when has no basic info
  189 + Given the following product_category
  190 + | name |
  191 + | Bicycle |
  192 + And the following products
  193 + | owner | category | name |
  194 + | redemoinho | bicycle | Bike |
  195 + And I am logged in as "joaosilva"
  196 + When I go to Rede Moinho's page of product Bike
  197 + Then I should see "Change category"
  198 + And I should see "Edit name"
  199 + And I should see "Add description, price and other basic information"
  200 + And I should see "Change image"
  201 +
  202 + Scenario: a not logged user cannot see edit links
  203 + Given I am not logged in
  204 + And the following product_category
  205 + | name |
  206 + | Bicycle |
  207 + And the following products
  208 + | owner | category | name | description |
  209 + | redemoinho | bicycle | Bike | Red bicycle |
  210 + When I go to Rede Moinho's page of product Bike
  211 + Then I should not see "Change category"
  212 + And I should not see "Edit name"
  213 + And I should not see "Edit basic information"
  214 + And I should not see "Change image"
  215 +
  216 + Scenario: a not allowed user cannot see edit links
  217 + Given the following users
  218 + | login | name |
  219 + | mariasantos | Maria Santos |
  220 + And the following product_category
  221 + | name |
  222 + | Bicycle |
  223 + And the following products
  224 + | owner | category | name | description |
  225 + | redemoinho | bicycle | Bike | Red bicycle |
  226 + And I am logged in as "mariasantos"
  227 + When I go to Rede Moinho's page of product Bike
  228 + Then I should not see "Change category"
  229 + And I should not see "Edit name"
  230 + And I should not see "Edit basic information"
  231 + And I should not see "Change image"
  232 +
  233 + @selenium
  234 + Scenario: edit name of a product
  235 + Given the following product_category
  236 + | name |
  237 + | Bicycle |
  238 + And the following products
  239 + | owner | category | name |
  240 + | redemoinho | bicycle | Bike |
  241 + And I am logged in as "joaosilva"
  242 + When I go to Rede Moinho's page of product Bike
  243 + And I follow "Edit name"
  244 + And I fill in "product_name" with "Red bicycle"
  245 + And I press "Save"
  246 + Then I should see "Red bicycle"
  247 + And I should be on Rede Moinho's page of product Red bicycle
  248 +
  249 + @selenium
  250 + Scenario: cancel edition of a product name
  251 + Given the following product_category
  252 + | name |
  253 + | Bicycle |
  254 + And the following products
  255 + | owner | category | name |
  256 + | redemoinho | bicycle | Bike |
  257 + And I am logged in as "joaosilva"
  258 + When I go to Rede Moinho's page of product Bike
  259 + And I follow "Edit name"
  260 + When I follow "Cancel"
  261 + Then I should see "Bike"
  262 +
  263 + @selenium
  264 + Scenario: edit category of a product
  265 + Given the following product_category
  266 + | name |
  267 + | Eletronics |
  268 + And the following product_categories
  269 + | name | parent |
  270 + | Computers | eletronics |
  271 + | DVDs | eletronics |
  272 + And the following products
  273 + | owner | category | name |
  274 + | redemoinho | computers | Generic pc |
  275 + And I am logged in as "joaosilva"
  276 + When I go to Rede Moinho's page of product Generic pc
  277 + And I follow "Change category"
  278 + And I select "Eletronics »"
  279 + Then I select "DVDs"
  280 + And I press "Save and continue"
  281 + Then I should see "Eletronics → DVDs"
  282 +
  283 + @selenium
  284 + Scenario: cancel edition of a product category
  285 + Given the following product_category
  286 + | name |
  287 + | Eletronics |
  288 + And the following product_categories
  289 + | name | parent |
  290 + | Computers | eletronics |
  291 + | DVDs | eletronics |
  292 + And the following products
  293 + | owner | category | name |
  294 + | redemoinho | computers | Generic pc |
  295 + And I am logged in as "joaosilva"
  296 + When I go to Rede Moinho's page of product Generic pc
  297 + And I follow "Change category"
  298 + When I follow "Back to product"
  299 + Then I should see "Eletronics → Computers"
  300 +
  301 +
  302 + @selenium
  303 + Scenario: edit image of a product
  304 + Given the following product_category
  305 + | name |
  306 + | Eletronics |
  307 + And the following product_categories
  308 + | name | parent |
  309 + | Computers | eletronics |
  310 + | DVDs | eletronics |
  311 + And the following products
  312 + | owner | category | name |
  313 + | redemoinho | computers | Generic pc |
  314 + And I am logged in as "joaosilva"
  315 + When I go to Rede Moinho's page of product Generic pc
  316 + And I follow "Change image"
  317 + When I follow "Cancel"
  318 + Then I should be on Rede Moinho's page of product Generic pc
  319 +
  320 + # FIXME Not working because of tinyMCE plus selenium
  321 + # @selenium
  322 + # Scenario: edit description of a product
  323 + # Given the following product_category
  324 + # | name |
  325 + # | Bicycle |
  326 + # And the following products
  327 + # | owner | category | name | description |
  328 + # | redemoinho | bicycle | Bike | A new red bicycle |
  329 + # And I am logged in as "joaosilva"
  330 + # When I go to Rede Moinho's page of product Bike
  331 + # Then I should see "A new red bicycle"
  332 + # And I follow "Edit basic information"
  333 + # And I type in tinyMCE field "Description" the text "An used red bicycle"
  334 + # And I press "Save"
  335 + # Then I should not see "A new red bicycle"
  336 + # And I should see "An used red bicycle"
  337 + # And I should be on Rede Moinho's page of product Bike
  338 +
  339 + @selenium
  340 + Scenario: cancel edition of a product description
  341 + Given the following product_category
  342 + | name |
  343 + | Bicycle |
  344 + And the following products
  345 + | owner | category | name | description |
  346 + | redemoinho | bicycle | Bike | A new red bicycle |
  347 + And I am logged in as "joaosilva"
  348 + When I go to Rede Moinho's page of product Bike
  349 + Then I should see "A new red bicycle"
  350 + And I follow "Edit basic information"
  351 + When I follow "Cancel"
  352 + Then I should see "A new red bicycle"
  353 + And I should be on Rede Moinho's page of product Bike
... ...
features/step_definitions/noosfero_steps.rb
... ... @@ -200,3 +200,5 @@ Given /^(.+) is disabled$/ do |enterprise_name|
200 200 enterprise.enabled = false
201 201 enterprise.save
202 202 end
  203 +
  204 +
... ...
features/support/paths.rb
... ... @@ -54,6 +54,14 @@ module NavigationHelpers
54 54 when /^(.+)'s new product page/
55 55 '/myprofile/%s/manage_products/new' % Profile.find_by_name($1).identifier
56 56  
  57 + when /^(.+)'s page of product (.*)$/
  58 + enterprise = Profile.find_by_name($1)
  59 + product = enterprise.products.find_by_name($2)
  60 + '/myprofile/%s/manage_products/show/%s' % [enterprise.identifier, product.id]
  61 +
  62 + when /^(.*)'s products page$/
  63 + '/catalog/%s' % Profile.find_by_name($1).identifier
  64 +
57 65 # Add more mappings here.
58 66 # Here is a more fancy example:
59 67 #
... ...
public/designs/templates/leftbar/stylesheets/style.css
1 1 .box-1 {
2   - margin: 0 0 0 200px;
  2 + margin: 0 30px 0 260px;
3 3 }
4 4  
5 5 .box-1 .blocks {
... ... @@ -17,3 +17,7 @@
17 17 padding: 10px 0;
18 18 float: none;
19 19 }
  20 +
  21 +#product-image {
  22 + margin-right: 0px;
  23 +}
... ...
public/designs/templates/rightbar/stylesheets/style.css
... ... @@ -9,7 +9,7 @@
9 9  
10 10 .box-2 {
11 11 float: right;
12   - width: 190px;
  12 + width: 230px;
13 13 }
14 14  
15 15 .block {
... ...
public/javascripts/application.js
... ... @@ -138,3 +138,58 @@ function update_loading(message) {
138 138 function redirect_to(url) {
139 139 document.location=url;
140 140 }
  141 +
  142 +/* Products edition */
  143 +
  144 +function numbersonly(e, separator) {
  145 + var key;
  146 + var keychar;
  147 +
  148 + if (window.event) {
  149 + key = window.event.keyCode;
  150 + }
  151 + else if (e) {
  152 + key = e.which;
  153 + }
  154 + else {
  155 + return true;
  156 + }
  157 + keychar = String.fromCharCode(key);
  158 +
  159 + if ((key==null) || (key==0) || (key==8) || (key==9) || (key==13) || (key==27) ) {
  160 + return true;
  161 + }
  162 + else if ((("0123456789").indexOf(keychar) > -1)) {
  163 + return true;
  164 + }
  165 + else if (keychar == separator) {
  166 + return true;
  167 + }
  168 + else
  169 + return false;
  170 +}
  171 +
  172 +// transform all element with class ui_button in a jQuery UI button
  173 +function render_jquery_ui_buttons(element_id) {
  174 + if (element_id) {
  175 + element_id = '#' + element_id
  176 + jQuery(element_id).button({
  177 + icons: {
  178 + primary: jQuery(element_id).attr('data-primary-icon'),
  179 + secondary: jQuery(element_id).attr('data-secondary-icon')
  180 + }
  181 + }
  182 + )
  183 + }
  184 + else {
  185 + jQuery('.ui_button').each(function() {
  186 + jQuery(this).button({
  187 + icons: {
  188 + primary: this.getAttribute('data-primary-icon'),
  189 + secondary: this.getAttribute('data-secondary-icon')
  190 + }
  191 + }
  192 + )
  193 + })
  194 + }
  195 +}
... ...
public/javascripts/jquery.form.js 0 → 100644
... ... @@ -0,0 +1,675 @@
  1 +/*!
  2 + * jQuery Form Plugin
  3 + * version: 2.43 (12-MAR-2010)
  4 + * @requires jQuery v1.3.2 or later
  5 + *
  6 + * Examples and documentation at: http://malsup.com/jquery/form/
  7 + * Dual licensed under the MIT and GPL licenses:
  8 + * http://www.opensource.org/licenses/mit-license.php
  9 + * http://www.gnu.org/licenses/gpl.html
  10 + */
  11 +;(function($) {
  12 +
  13 +/*
  14 + Usage Note:
  15 + -----------
  16 + Do not use both ajaxSubmit and ajaxForm on the same form. These
  17 + functions are intended to be exclusive. Use ajaxSubmit if you want
  18 + to bind your own submit handler to the form. For example,
  19 +
  20 + $(document).ready(function() {
  21 + $('#myForm').bind('submit', function() {
  22 + $(this).ajaxSubmit({
  23 + target: '#output'
  24 + });
  25 + return false; // <-- important!
  26 + });
  27 + });
  28 +
  29 + Use ajaxForm when you want the plugin to manage all the event binding
  30 + for you. For example,
  31 +
  32 + $(document).ready(function() {
  33 + $('#myForm').ajaxForm({
  34 + target: '#output'
  35 + });
  36 + });
  37 +
  38 + When using ajaxForm, the ajaxSubmit function will be invoked for you
  39 + at the appropriate time.
  40 +*/
  41 +
  42 +/**
  43 + * ajaxSubmit() provides a mechanism for immediately submitting
  44 + * an HTML form using AJAX.
  45 + */
  46 +$.fn.ajaxSubmit = function(options) {
  47 + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
  48 + if (!this.length) {
  49 + log('ajaxSubmit: skipping submit process - no element selected');
  50 + return this;
  51 + }
  52 +
  53 + if (typeof options == 'function')
  54 + options = { success: options };
  55 +
  56 + var url = $.trim(this.attr('action'));
  57 + if (url) {
  58 + // clean url (don't include hash vaue)
  59 + url = (url.match(/^([^#]+)/)||[])[1];
  60 + }
  61 + url = url || window.location.href || '';
  62 +
  63 + options = $.extend({
  64 + url: url,
  65 + type: this.attr('method') || 'GET',
  66 + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
  67 + }, options || {});
  68 +
  69 + // hook for manipulating the form data before it is extracted;
  70 + // convenient for use with rich editors like tinyMCE or FCKEditor
  71 + var veto = {};
  72 + this.trigger('form-pre-serialize', [this, options, veto]);
  73 + if (veto.veto) {
  74 + log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
  75 + return this;
  76 + }
  77 +
  78 + // provide opportunity to alter form data before it is serialized
  79 + if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
  80 + log('ajaxSubmit: submit aborted via beforeSerialize callback');
  81 + return this;
  82 + }
  83 +
  84 + var a = this.formToArray(options.semantic);
  85 + if (options.data) {
  86 + options.extraData = options.data;
  87 + for (var n in options.data) {
  88 + if(options.data[n] instanceof Array) {
  89 + for (var k in options.data[n])
  90 + a.push( { name: n, value: options.data[n][k] } );
  91 + }
  92 + else
  93 + a.push( { name: n, value: options.data[n] } );
  94 + }
  95 + }
  96 +
  97 + // give pre-submit callback an opportunity to abort the submit
  98 + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
  99 + log('ajaxSubmit: submit aborted via beforeSubmit callback');
  100 + return this;
  101 + }
  102 +
  103 + // fire vetoable 'validate' event
  104 + this.trigger('form-submit-validate', [a, this, options, veto]);
  105 + if (veto.veto) {
  106 + log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
  107 + return this;
  108 + }
  109 +
  110 + var q = $.param(a);
  111 +
  112 + if (options.type.toUpperCase() == 'GET') {
  113 + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
  114 + options.data = null; // data is null for 'get'
  115 + }
  116 + else
  117 + options.data = q; // data is the query string for 'post'
  118 +
  119 + var $form = this, callbacks = [];
  120 + if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
  121 + if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
  122 +
  123 + // perform a load on the target only if dataType is not provided
  124 + if (!options.dataType && options.target) {
  125 + var oldSuccess = options.success || function(){};
  126 + callbacks.push(function(data) {
  127 + var fn = options.replaceTarget ? 'replaceWith' : 'html';
  128 + $(options.target)[fn](data).each(oldSuccess, arguments);
  129 + });
  130 + }
  131 + else if (options.success)
  132 + callbacks.push(options.success);
  133 +
  134 + options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
  135 + for (var i=0, max=callbacks.length; i < max; i++)
  136 + callbacks[i].apply(options, [data, status, xhr || $form, $form]);
  137 + };
  138 +
  139 + // are there files to upload?
  140 + var files = $('input:file', this).fieldValue();
  141 + var found = false;
  142 + for (var j=0; j < files.length; j++)
  143 + if (files[j])
  144 + found = true;
  145 +
  146 + var multipart = false;
  147 +// var mp = 'multipart/form-data';
  148 +// multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
  149 +
  150 + // options.iframe allows user to force iframe mode
  151 + // 06-NOV-09: now defaulting to iframe mode if file input is detected
  152 + if ((files.length && options.iframe !== false) || options.iframe || found || multipart) {
  153 + // hack to fix Safari hang (thanks to Tim Molendijk for this)
  154 + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
  155 + if (options.closeKeepAlive)
  156 + $.get(options.closeKeepAlive, fileUpload);
  157 + else
  158 + fileUpload();
  159 + }
  160 + else
  161 + $.ajax(options);
  162 +
  163 + // fire 'notify' event
  164 + this.trigger('form-submit-notify', [this, options]);
  165 + return this;
  166 +
  167 +
  168 + // private function for handling file uploads (hat tip to YAHOO!)
  169 + function fileUpload() {
  170 + var form = $form[0];
  171 +
  172 + if ($(':input[name=submit]', form).length) {
  173 + alert('Error: Form elements must not be named "submit".');
  174 + return;
  175 + }
  176 +
  177 + var opts = $.extend({}, $.ajaxSettings, options);
  178 + var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);
  179 +
  180 + var id = 'jqFormIO' + (new Date().getTime());
  181 + var $io = $('<iframe id="' + id + '" name="' + id + '" src="'+ opts.iframeSrc +'" onload="(jQuery(this).data(\'form-plugin-onload\'))()" />');
  182 + var io = $io[0];
  183 +
  184 + $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
  185 +
  186 + var xhr = { // mock object
  187 + aborted: 0,
  188 + responseText: null,
  189 + responseXML: null,
  190 + status: 0,
  191 + statusText: 'n/a',
  192 + getAllResponseHeaders: function() {},
  193 + getResponseHeader: function() {},
  194 + setRequestHeader: function() {},
  195 + abort: function() {
  196 + this.aborted = 1;
  197 + $io.attr('src', opts.iframeSrc); // abort op in progress
  198 + }
  199 + };
  200 +
  201 + var g = opts.global;
  202 + // trigger ajax global events so that activity/block indicators work like normal
  203 + if (g && ! $.active++) $.event.trigger("ajaxStart");
  204 + if (g) $.event.trigger("ajaxSend", [xhr, opts]);
  205 +
  206 + if (s.beforeSend && s.beforeSend(xhr, s) === false) {
  207 + s.global && $.active--;
  208 + return;
  209 + }
  210 + if (xhr.aborted)
  211 + return;
  212 +
  213 + var cbInvoked = false;
  214 + var timedOut = 0;
  215 +
  216 + // add submitting element to data if we know it
  217 + var sub = form.clk;
  218 + if (sub) {
  219 + var n = sub.name;
  220 + if (n && !sub.disabled) {
  221 + opts.extraData = opts.extraData || {};
  222 + opts.extraData[n] = sub.value;
  223 + if (sub.type == "image") {
  224 + opts.extraData[n+'.x'] = form.clk_x;
  225 + opts.extraData[n+'.y'] = form.clk_y;
  226 + }
  227 + }
  228 + }
  229 +
  230 + // take a breath so that pending repaints get some cpu time before the upload starts
  231 + function doSubmit() {
  232 + // make sure form attrs are set
  233 + var t = $form.attr('target'), a = $form.attr('action');
  234 +
  235 + // update form attrs in IE friendly way
  236 + form.setAttribute('target',id);
  237 + if (form.getAttribute('method') != 'POST')
  238 + form.setAttribute('method', 'POST');
  239 + if (form.getAttribute('action') != opts.url)
  240 + form.setAttribute('action', opts.url);
  241 +
  242 + // ie borks in some cases when setting encoding
  243 + if (! opts.skipEncodingOverride) {
  244 + $form.attr({
  245 + encoding: 'multipart/form-data',
  246 + enctype: 'multipart/form-data'
  247 + });
  248 + }
  249 +
  250 + // support timout
  251 + if (opts.timeout)
  252 + setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
  253 +
  254 + // add "extra" data to form if provided in options
  255 + var extraInputs = [];
  256 + try {
  257 + if (opts.extraData)
  258 + for (var n in opts.extraData)
  259 + extraInputs.push(
  260 + $('<input type="hidden" name="'+n+'" value="'+opts.extraData[n]+'" />')
  261 + .appendTo(form)[0]);
  262 +
  263 + // add iframe to doc and submit the form
  264 + $io.appendTo('body');
  265 + $io.data('form-plugin-onload', cb);
  266 + form.submit();
  267 + }
  268 + finally {
  269 + // reset attrs and remove "extra" input elements
  270 + form.setAttribute('action',a);
  271 + t ? form.setAttribute('target', t) : $form.removeAttr('target');
  272 + $(extraInputs).remove();
  273 + }
  274 + };
  275 +
  276 + if (opts.forceSync)
  277 + doSubmit();
  278 + else
  279 + setTimeout(doSubmit, 10); // this lets dom updates render
  280 +
  281 + var domCheckCount = 100;
  282 +
  283 + function cb() {
  284 + if (cbInvoked)
  285 + return;
  286 +
  287 + var ok = true;
  288 + try {
  289 + if (timedOut) throw 'timeout';
  290 + // extract the server response from the iframe
  291 + var data, doc;
  292 +
  293 + doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
  294 +
  295 + var isXml = opts.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
  296 + log('isXml='+isXml);
  297 + if (!isXml && (doc.body == null || doc.body.innerHTML == '')) {
  298 + if (--domCheckCount) {
  299 + // in some browsers (Opera) the iframe DOM is not always traversable when
  300 + // the onload callback fires, so we loop a bit to accommodate
  301 + log('requeing onLoad callback, DOM not available');
  302 + setTimeout(cb, 250);
  303 + return;
  304 + }
  305 + log('Could not access iframe DOM after 100 tries.');
  306 + return;
  307 + }
  308 +
  309 + log('response detected');
  310 + cbInvoked = true;
  311 + xhr.responseText = doc.body ? doc.body.innerHTML : null;
  312 + xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
  313 + xhr.getResponseHeader = function(header){
  314 + var headers = {'content-type': opts.dataType};
  315 + return headers[header];
  316 + };
  317 +
  318 + if (opts.dataType == 'json' || opts.dataType == 'script') {
  319 + // see if user embedded response in textarea
  320 + var ta = doc.getElementsByTagName('textarea')[0];
  321 + if (ta)
  322 + xhr.responseText = ta.value;
  323 + else {
  324 + // account for browsers injecting pre around json response
  325 + var pre = doc.getElementsByTagName('pre')[0];
  326 + if (pre)
  327 + xhr.responseText = pre.innerHTML;
  328 + }
  329 + }
  330 + else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
  331 + xhr.responseXML = toXml(xhr.responseText);
  332 + }
  333 + data = $.httpData(xhr, opts.dataType);
  334 + }
  335 + catch(e){
  336 + log('error caught:',e);
  337 + ok = false;
  338 + xhr.error = e;
  339 + $.handleError(opts, xhr, 'error', e);
  340 + }
  341 +
  342 + // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
  343 + if (ok) {
  344 + opts.success(data, 'success');
  345 + if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
  346 + }
  347 + if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
  348 + if (g && ! --$.active) $.event.trigger("ajaxStop");
  349 + if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
  350 +
  351 + // clean up
  352 + setTimeout(function() {
  353 + $io.removeData('form-plugin-onload');
  354 + $io.remove();
  355 + xhr.responseXML = null;
  356 + }, 100);
  357 + };
  358 +
  359 + function toXml(s, doc) {
  360 + if (window.ActiveXObject) {
  361 + doc = new ActiveXObject('Microsoft.XMLDOM');
  362 + doc.async = 'false';
  363 + doc.loadXML(s);
  364 + }
  365 + else
  366 + doc = (new DOMParser()).parseFromString(s, 'text/xml');
  367 + return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
  368 + };
  369 + };
  370 +};
  371 +
  372 +/**
  373 + * ajaxForm() provides a mechanism for fully automating form submission.
  374 + *
  375 + * The advantages of using this method instead of ajaxSubmit() are:
  376 + *
  377 + * 1: This method will include coordinates for <input type="image" /> elements (if the element
  378 + * is used to submit the form).
  379 + * 2. This method will include the submit element's name/value data (for the element that was
  380 + * used to submit the form).
  381 + * 3. This method binds the submit() method to the form for you.
  382 + *
  383 + * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
  384 + * passes the options argument along after properly binding events for submit elements and
  385 + * the form itself.
  386 + */
  387 +$.fn.ajaxForm = function(options) {
  388 + return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
  389 + e.preventDefault();
  390 + $(this).ajaxSubmit(options);
  391 + }).bind('click.form-plugin', function(e) {
  392 + var target = e.target;
  393 + var $el = $(target);
  394 + if (!($el.is(":submit,input:image"))) {
  395 + // is this a child element of the submit el? (ex: a span within a button)
  396 + var t = $el.closest(':submit');
  397 + if (t.length == 0)
  398 + return;
  399 + target = t[0];
  400 + }
  401 + var form = this;
  402 + form.clk = target;
  403 + if (target.type == 'image') {
  404 + if (e.offsetX != undefined) {
  405 + form.clk_x = e.offsetX;
  406 + form.clk_y = e.offsetY;
  407 + } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
  408 + var offset = $el.offset();
  409 + form.clk_x = e.pageX - offset.left;
  410 + form.clk_y = e.pageY - offset.top;
  411 + } else {
  412 + form.clk_x = e.pageX - target.offsetLeft;
  413 + form.clk_y = e.pageY - target.offsetTop;
  414 + }
  415 + }
  416 + // clear form vars
  417 + setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
  418 + });
  419 +};
  420 +
  421 +// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
  422 +$.fn.ajaxFormUnbind = function() {
  423 + return this.unbind('submit.form-plugin click.form-plugin');
  424 +};
  425 +
  426 +/**
  427 + * formToArray() gathers form element data into an array of objects that can
  428 + * be passed to any of the following ajax functions: $.get, $.post, or load.
  429 + * Each object in the array has both a 'name' and 'value' property. An example of
  430 + * an array for a simple login form might be:
  431 + *
  432 + * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
  433 + *
  434 + * It is this array that is passed to pre-submit callback functions provided to the
  435 + * ajaxSubmit() and ajaxForm() methods.
  436 + */
  437 +$.fn.formToArray = function(semantic) {
  438 + var a = [];
  439 + if (this.length == 0) return a;
  440 +
  441 + var form = this[0];
  442 + var els = semantic ? form.getElementsByTagName('*') : form.elements;
  443 + if (!els) return a;
  444 + for(var i=0, max=els.length; i < max; i++) {
  445 + var el = els[i];
  446 + var n = el.name;
  447 + if (!n) continue;
  448 +
  449 + if (semantic && form.clk && el.type == "image") {
  450 + // handle image inputs on the fly when semantic == true
  451 + if(!el.disabled && form.clk == el) {
  452 + a.push({name: n, value: $(el).val()});
  453 + a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
  454 + }
  455 + continue;
  456 + }
  457 +
  458 + var v = $.fieldValue(el, true);
  459 + if (v && v.constructor == Array) {
  460 + for(var j=0, jmax=v.length; j < jmax; j++)
  461 + a.push({name: n, value: v[j]});
  462 + }
  463 + else if (v !== null && typeof v != 'undefined')
  464 + a.push({name: n, value: v});
  465 + }
  466 +
  467 + if (!semantic && form.clk) {
  468 + // input type=='image' are not found in elements array! handle it here
  469 + var $input = $(form.clk), input = $input[0], n = input.name;
  470 + if (n && !input.disabled && input.type == 'image') {
  471 + a.push({name: n, value: $input.val()});
  472 + a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
  473 + }
  474 + }
  475 + return a;
  476 +};
  477 +
  478 +/**
  479 + * Serializes form data into a 'submittable' string. This method will return a string
  480 + * in the format: name1=value1&amp;name2=value2
  481 + */
  482 +$.fn.formSerialize = function(semantic) {
  483 + //hand off to jQuery.param for proper encoding
  484 + return $.param(this.formToArray(semantic));
  485 +};
  486 +
  487 +/**
  488 + * Serializes all field elements in the jQuery object into a query string.
  489 + * This method will return a string in the format: name1=value1&amp;name2=value2
  490 + */
  491 +$.fn.fieldSerialize = function(successful) {
  492 + var a = [];
  493 + this.each(function() {
  494 + var n = this.name;
  495 + if (!n) return;
  496 + var v = $.fieldValue(this, successful);
  497 + if (v && v.constructor == Array) {
  498 + for (var i=0,max=v.length; i < max; i++)
  499 + a.push({name: n, value: v[i]});
  500 + }
  501 + else if (v !== null && typeof v != 'undefined')
  502 + a.push({name: this.name, value: v});
  503 + });
  504 + //hand off to jQuery.param for proper encoding
  505 + return $.param(a);
  506 +};
  507 +
  508 +/**
  509 + * Returns the value(s) of the element in the matched set. For example, consider the following form:
  510 + *
  511 + * <form><fieldset>
  512 + * <input name="A" type="text" />
  513 + * <input name="A" type="text" />
  514 + * <input name="B" type="checkbox" value="B1" />
  515 + * <input name="B" type="checkbox" value="B2"/>
  516 + * <input name="C" type="radio" value="C1" />
  517 + * <input name="C" type="radio" value="C2" />
  518 + * </fieldset></form>
  519 + *
  520 + * var v = $(':text').fieldValue();
  521 + * // if no values are entered into the text inputs
  522 + * v == ['','']
  523 + * // if values entered into the text inputs are 'foo' and 'bar'
  524 + * v == ['foo','bar']
  525 + *
  526 + * var v = $(':checkbox').fieldValue();
  527 + * // if neither checkbox is checked
  528 + * v === undefined
  529 + * // if both checkboxes are checked
  530 + * v == ['B1', 'B2']
  531 + *
  532 + * var v = $(':radio').fieldValue();
  533 + * // if neither radio is checked
  534 + * v === undefined
  535 + * // if first radio is checked
  536 + * v == ['C1']
  537 + *
  538 + * The successful argument controls whether or not the field element must be 'successful'
  539 + * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
  540 + * The default value of the successful argument is true. If this value is false the value(s)
  541 + * for each element is returned.
  542 + *
  543 + * Note: This method *always* returns an array. If no valid value can be determined the
  544 + * array will be empty, otherwise it will contain one or more values.
  545 + */
  546 +$.fn.fieldValue = function(successful) {
  547 + for (var val=[], i=0, max=this.length; i < max; i++) {
  548 + var el = this[i];
  549 + var v = $.fieldValue(el, successful);
  550 + if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
  551 + continue;
  552 + v.constructor == Array ? $.merge(val, v) : val.push(v);
  553 + }
  554 + return val;
  555 +};
  556 +
  557 +/**
  558 + * Returns the value of the field element.
  559 + */
  560 +$.fieldValue = function(el, successful) {
  561 + var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
  562 + if (typeof successful == 'undefined') successful = true;
  563 +
  564 + if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
  565 + (t == 'checkbox' || t == 'radio') && !el.checked ||
  566 + (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
  567 + tag == 'select' && el.selectedIndex == -1))
  568 + return null;
  569 +
  570 + if (tag == 'select') {
  571 + var index = el.selectedIndex;
  572 + if (index < 0) return null;
  573 + var a = [], ops = el.options;
  574 + var one = (t == 'select-one');
  575 + var max = (one ? index+1 : ops.length);
  576 + for(var i=(one ? index : 0); i < max; i++) {
  577 + var op = ops[i];
  578 + if (op.selected) {
  579 + var v = op.value;
  580 + if (!v) // extra pain for IE...
  581 + v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
  582 + if (one) return v;
  583 + a.push(v);
  584 + }
  585 + }
  586 + return a;
  587 + }
  588 + return el.value;
  589 +};
  590 +
  591 +/**
  592 + * Clears the form data. Takes the following actions on the form's input fields:
  593 + * - input text fields will have their 'value' property set to the empty string
  594 + * - select elements will have their 'selectedIndex' property set to -1
  595 + * - checkbox and radio inputs will have their 'checked' property set to false
  596 + * - inputs of type submit, button, reset, and hidden will *not* be effected
  597 + * - button elements will *not* be effected
  598 + */
  599 +$.fn.clearForm = function() {
  600 + return this.each(function() {
  601 + $('input,select,textarea', this).clearFields();
  602 + });
  603 +};
  604 +
  605 +/**
  606 + * Clears the selected form elements.
  607 + */
  608 +$.fn.clearFields = $.fn.clearInputs = function() {
  609 + return this.each(function() {
  610 + var t = this.type, tag = this.tagName.toLowerCase();
  611 + if (t == 'text' || t == 'password' || tag == 'textarea')
  612 + this.value = '';
  613 + else if (t == 'checkbox' || t == 'radio')
  614 + this.checked = false;
  615 + else if (tag == 'select')
  616 + this.selectedIndex = -1;
  617 + });
  618 +};
  619 +
  620 +/**
  621 + * Resets the form data. Causes all form elements to be reset to their original value.
  622 + */
  623 +$.fn.resetForm = function() {
  624 + return this.each(function() {
  625 + // guard against an input with the name of 'reset'
  626 + // note that IE reports the reset function as an 'object'
  627 + if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
  628 + this.reset();
  629 + });
  630 +};
  631 +
  632 +/**
  633 + * Enables or disables any matching elements.
  634 + */
  635 +$.fn.enable = function(b) {
  636 + if (b == undefined) b = true;
  637 + return this.each(function() {
  638 + this.disabled = !b;
  639 + });
  640 +};
  641 +
  642 +/**
  643 + * Checks/unchecks any matching checkboxes or radio buttons and
  644 + * selects/deselects and matching option elements.
  645 + */
  646 +$.fn.selected = function(select) {
  647 + if (select == undefined) select = true;
  648 + return this.each(function() {
  649 + var t = this.type;
  650 + if (t == 'checkbox' || t == 'radio')
  651 + this.checked = select;
  652 + else if (this.tagName.toLowerCase() == 'option') {
  653 + var $sel = $(this).parent('select');
  654 + if (select && $sel[0] && $sel[0].type == 'select-one') {
  655 + // deselect all other options
  656 + $sel.find('option').selected(false);
  657 + }
  658 + this.selected = select;
  659 + }
  660 + });
  661 +};
  662 +
  663 +// helper fn for console logging
  664 +// set $.fn.ajaxSubmit.debug to true to enable debug logging
  665 +function log() {
  666 + if ($.fn.ajaxSubmit.debug) {
  667 + var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
  668 + if (window.console && window.console.log)
  669 + window.console.log(msg);
  670 + else if (window.opera && window.opera.postError)
  671 + window.opera.postError(msg);
  672 + }
  673 +};
  674 +
  675 +})(jQuery);
... ...
public/stylesheets/application.css
... ... @@ -97,7 +97,7 @@ div#profile-disabled {
97 97 padding-left: 60px;
98 98 min-height: 40px;
99 99 width: 400px;
100   - background: url("../images/icons-app/alert.png") no-repeat 5px #ffffa9;
  100 + background: url('/images/icons-app/alert.png') no-repeat 5px #ffffa9;
101 101 }
102 102  
103 103 div#profile-disabled .unlock-button{
... ... @@ -2503,20 +2503,158 @@ div#activation_enterprise div {
2503 2503  
2504 2504 /* * * Show Product * * * * * * * * * * * * */
2505 2505  
2506   -#show_product .product-pic {
  2506 +.controller-catalog #show_product .product-pic {
  2507 + border: 1px solid #D3D7CF;
  2508 +}
  2509 +
  2510 +#show_product,
  2511 +#category-product-edition {
  2512 + background-color: transparent;
  2513 + position: relative;
  2514 + padding: 5px;
  2515 + -moz-border-radius: 5px;
  2516 + -webkit-border-radius: 5px;
  2517 +}
  2518 +
  2519 +#show_product h2,
  2520 +#category-product-edition h2 {
  2521 + margin-top: 0px;
  2522 +}
  2523 +
  2524 +#show_product .field-value {
  2525 + font-weight: bold;
  2526 +}
  2527 +
  2528 +#product-category,
  2529 +.product-category-hierarchy {
  2530 + margin-top: 40px;
  2531 + margin-bottom: 12px;
  2532 + padding-left: 7px;
  2533 +}
  2534 +
  2535 +#product-category {
  2536 + height: 18px;
  2537 +}
  2538 +
  2539 +#product-category p {
  2540 + display: inline;
  2541 +}
  2542 +
  2543 +#category-product-edition {
  2544 + padding-left: 10px;
  2545 + padding-right: 10px;
  2546 +}
  2547 +
  2548 +#display-product-name,
  2549 +#display-product-category {
2507 2550 float: left;
2508   - margin-right: 15px;
2509   - border: 1px solid #444;
2510   - padding: 2px;
2511   - background: #FFF;
2512 2551 }
2513 2552  
2514   -#show_product .product_category {
  2553 +#product-details {
2515 2554 padding-top: 10px;
  2555 + padding-bottom: 10px;
  2556 + position: relative;
  2557 +}
  2558 +
  2559 +#product-name h2 {
  2560 + display: inline;
  2561 +}
  2562 +
  2563 +#product_name {
  2564 + width: 90%;
  2565 +}
  2566 +
  2567 +#edit-product-name {
  2568 + margin-top: 10px;
  2569 +}
  2570 +
  2571 +#product-name,
  2572 +#product-details {
  2573 + position: relative;
2516 2574 clear: left;
2517 2575 }
2518 2576  
  2577 +#display-product-info {
  2578 + left: 290px;
  2579 + top: 10px;
  2580 + width: 360px;
  2581 + text-align: left;
  2582 + position: absolute;
  2583 + overflow: hidden;
  2584 +}
  2585 +
  2586 +#product-info-form {
  2587 + margin-top: 20px;
  2588 +}
2519 2589  
  2590 +#edit-product-category {
  2591 + padding: 15px 5px 10px;
  2592 +}
  2593 +
  2594 +#product-info .field-name {
  2595 + margin-top: 5px;
  2596 + margin-bottom: 15px;
  2597 +}
  2598 +
  2599 +#product-info .type-text input,
  2600 +#product-info select {
  2601 + width: 150px;
  2602 +}
  2603 +
  2604 +#product-info .section-title {
  2605 + display: none;
  2606 +}
  2607 +
  2608 +#product-qualifiers {
  2609 + padding-left: 0px;
  2610 +}
  2611 +
  2612 +#product-qualifiers li {
  2613 + list-style: none;
  2614 +}
  2615 +
  2616 +#edit-product-button-ui-info {
  2617 + margin-top: 40px;
  2618 +}
  2619 +
  2620 +#product-image {
  2621 + text-align: center;
  2622 + padding: 10px;
  2623 + width: 250px;
  2624 + margin-right: 10px;
  2625 + margin-bottom: 5px;
  2626 + border: 1px solid #AAA;
  2627 +}
  2628 +
  2629 +#product-image p {
  2630 + font-size: 11px;
  2631 + font-style: italic;
  2632 + margin-top: 0px;
  2633 + color: #555;
  2634 +}
  2635 +
  2636 +#display-product-image {
  2637 + margin-bottom: 10px;
  2638 + overflow: hidden;
  2639 +}
  2640 +
  2641 +#edit-product-image {
  2642 + position: relative;
  2643 + margin-top: 5px;
  2644 +}
  2645 +
  2646 +#product-qualifiers-list {
  2647 + padding: 0px;
  2648 +}
  2649 +
  2650 +#product-qualifiers-list li {
  2651 + list-style: none;
  2652 +}
  2653 +
  2654 +#product-qualifiers-list .product-qualifier-title,
  2655 +#product-qualifiers-list .product-certifier-title {
  2656 + margin-right: 20px;
  2657 +}
2520 2658 /* ==> public/stylesheets/controller_cms.css <== */
2521 2659  
2522 2660 .controller-cms #article_types li {
... ... @@ -3007,7 +3145,7 @@ h1#agenda-title {
3007 3145 #new_product_title {
3008 3146 text-align: center;
3009 3147 }
3010   -#new_product_form {
  3148 +#product_category_form {
3011 3149 border: 1px solid #AABB88;
3012 3150 -moz-border-radius: 5px;
3013 3151 -webkit-border-radius: 5px;
... ... @@ -3063,7 +3201,7 @@ h1#agenda-title {
3063 3201 display: block;
3064 3202 }
3065 3203 #hierarchy_navigation {
3066   - min-height: 1em;
  3204 + min-height: 1.3em;
3067 3205 }
3068 3206 .msie #hierarchy_navigation {
3069 3207 font-size: small;
... ... @@ -3903,7 +4041,8 @@ h1#agenda-title {
3903 4041 /* jquery.ui custom styles */
3904 4042  
3905 4043 .fg-button span {
3906   - padding: 0.1em 1em !important;
  4044 + padding-top: 0.1em !important;
  4045 + padding-bottom: 0.1em !important;
3907 4046 }
3908 4047  
3909 4048 .treeitem .button{
... ...
public/stylesheets/jquery.ui/south-street/images/ui-anim_basic_16x16.gif 0 → 100644

1.52 KB

public/stylesheets/jquery.ui/south-street/images/ui-bg_glass_55_fcf0ba_1x400.png 0 → 100644

127 Bytes

public/stylesheets/jquery.ui/south-street/images/ui-bg_gloss-wave_100_ece8da_500x100.png 0 → 100644

2.08 KB

public/stylesheets/jquery.ui/south-street/images/ui-bg_highlight-hard_100_f5f3e5_1x100.png 0 → 100644

110 Bytes

public/stylesheets/jquery.ui/south-street/images/ui-bg_highlight-hard_100_fafaf4_1x100.png 0 → 100644

96 Bytes

public/stylesheets/jquery.ui/south-street/images/ui-bg_highlight-hard_15_459e00_1x100.png 0 → 100644

153 Bytes

public/stylesheets/jquery.ui/south-street/images/ui-bg_highlight-hard_95_cccccc_1x100.png 0 → 100644

105 Bytes

public/stylesheets/jquery.ui/south-street/images/ui-bg_highlight-soft_25_67b021_1x100.png 0 → 100644

124 Bytes

public/stylesheets/jquery.ui/south-street/images/ui-bg_highlight-soft_95_ffedad_1x100.png 0 → 100644

165 Bytes

public/stylesheets/jquery.ui/south-street/images/ui-bg_inset-soft_15_2b2922_1x100.png 0 → 100644

119 Bytes

public/stylesheets/jquery.ui/south-street/images/ui-icons_808080_256x240.png 0 → 100644

4.27 KB

public/stylesheets/jquery.ui/south-street/images/ui-icons_847e71_256x240.png 0 → 100644

4.27 KB

public/stylesheets/jquery.ui/south-street/images/ui-icons_8dc262_256x240.png 0 → 100644

5.23 KB

public/stylesheets/jquery.ui/south-street/images/ui-icons_cd0a0a_256x240.png 0 → 100644

4.27 KB

public/stylesheets/jquery.ui/south-street/images/ui-icons_eeeeee_256x240.png 0 → 100644

4.27 KB

public/stylesheets/jquery.ui/south-street/images/ui-icons_ffffff_256x240.png 0 → 100644

4.27 KB

public/stylesheets/jquery.ui/south-street/jquery-ui-1.8.2.custom.css 0 → 100644
... ... @@ -0,0 +1,489 @@
  1 +/*
  2 +* jQuery UI CSS Framework
  3 +* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
  4 +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
  5 +*/
  6 +
  7 +/* Layout helpers
  8 +----------------------------------*/
  9 +.ui-helper-hidden { display: none; }
  10 +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
  11 +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
  12 +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
  13 +.ui-helper-clearfix { display: inline-block; }
  14 +/* required comment for clearfix to work in Opera \*/
  15 +* html .ui-helper-clearfix { height:1%; }
  16 +.ui-helper-clearfix { display:block; }
  17 +/* end clearfix */
  18 +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
  19 +
  20 +
  21 +/* Interaction Cues
  22 +----------------------------------*/
  23 +.ui-state-disabled { cursor: default !important; }
  24 +
  25 +
  26 +/* Icons
  27 +----------------------------------*/
  28 +
  29 +/* states and images */
  30 +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
  31 +
  32 +
  33 +/* Misc visuals
  34 +----------------------------------*/
  35 +
  36 +/* Overlays */
  37 +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
  38 +
  39 +
  40 +/*
  41 +* jQuery UI CSS Framework
  42 +* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
  43 +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
  44 +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=segoe%20ui,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=6px&bgColorHeader=ece8da&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=100&borderColorHeader=d4ccb0&fcHeader=433f38&iconColorHeader=847e71&bgColorContent=f5f3e5&bgTextureContent=04_highlight_hard.png&bgImgOpacityContent=100&borderColorContent=dfd9c3&fcContent=312e25&iconColorContent=808080&bgColorDefault=459e00&bgTextureDefault=04_highlight_hard.png&bgImgOpacityDefault=15&borderColorDefault=327E04&fcDefault=ffffff&iconColorDefault=eeeeee&bgColorHover=67b021&bgTextureHover=03_highlight_soft.png&bgImgOpacityHover=25&borderColorHover=327E04&fcHover=ffffff&iconColorHover=ffffff&bgColorActive=fafaf4&bgTextureActive=04_highlight_hard.png&bgImgOpacityActive=100&borderColorActive=d4ccb0&fcActive=459e00&iconColorActive=8DC262&bgColorHighlight=fcf0ba&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=e8e1b5&fcHighlight=363636&iconColorHighlight=8DC262&bgColorError=ffedad&bgTextureError=03_highlight_soft.png&bgImgOpacityError=95&borderColorError=e3a345&fcError=cd5c0a&iconColorError=cd0a0a&bgColorOverlay=2b2922&bgTextureOverlay=05_inset_soft.png&bgImgOpacityOverlay=15&opacityOverlay=90&bgColorShadow=cccccc&bgTextureShadow=04_highlight_hard.png&bgImgOpacityShadow=95&opacityShadow=20&thicknessShadow=12px&offsetTopShadow=-12px&offsetLeftShadow=-12px&cornerRadiusShadow=10px
  45 +*/
  46 +
  47 +
  48 +/* Component containers
  49 +----------------------------------*/
  50 +.ui-widget { font-family: segoe ui, Arial, sans-serif; font-size: 1.1em; }
  51 +.ui-widget .ui-widget { font-size: 1em; }
  52 +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: segoe ui, Arial, sans-serif; font-size: 1em; }
  53 +.ui-widget-content { border: 1px solid #dfd9c3; background: #f5f3e5 url(images/ui-bg_highlight-hard_100_f5f3e5_1x100.png) 50% top repeat-x; color: #312e25; }
  54 +.ui-widget-content a { color: #312e25; }
  55 +.ui-widget-header { border: 1px solid #d4ccb0; background: #ece8da url(images/ui-bg_gloss-wave_100_ece8da_500x100.png) 50% 50% repeat-x; color: #433f38; font-weight: bold; }
  56 +.ui-widget-header a { color: #433f38; }
  57 +
  58 +/* Interaction states
  59 +----------------------------------*/
  60 +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #327e04; background: #459e00 url(images/ui-bg_highlight-hard_15_459e00_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #ffffff; }
  61 +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #ffffff; text-decoration: none; }
  62 +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #327e04; background: #67b021 url(images/ui-bg_highlight-soft_25_67b021_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #ffffff; }
  63 +.ui-state-hover a, .ui-state-hover a:hover { color: #ffffff; text-decoration: none; }
  64 +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #d4ccb0; background: #fafaf4 url(images/ui-bg_highlight-hard_100_fafaf4_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #459e00; }
  65 +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #459e00; text-decoration: none; }
  66 +.ui-widget :active { outline: none; }
  67 +
  68 +/* Interaction Cues
  69 +----------------------------------*/
  70 +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #e8e1b5; background: #fcf0ba url(images/ui-bg_glass_55_fcf0ba_1x400.png) 50% 50% repeat-x; color: #363636; }
  71 +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
  72 +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #e3a345; background: #ffedad url(images/ui-bg_highlight-soft_95_ffedad_1x100.png) 50% top repeat-x; color: #cd5c0a; }
  73 +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd5c0a; }
  74 +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd5c0a; }
  75 +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
  76 +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
  77 +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
  78 +
  79 +/* Icons
  80 +----------------------------------*/
  81 +
  82 +/* states and images */
  83 +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_808080_256x240.png); }
  84 +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_808080_256x240.png); }
  85 +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_847e71_256x240.png); }
  86 +.ui-state-default .ui-icon { background-image: url(images/ui-icons_eeeeee_256x240.png); }
  87 +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
  88 +.ui-state-active .ui-icon {background-image: url(images/ui-icons_8dc262_256x240.png); }
  89 +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_8dc262_256x240.png); }
  90 +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
  91 +
  92 +/* positioning */
  93 +.ui-icon-carat-1-n { background-position: 0 0; }
  94 +.ui-icon-carat-1-ne { background-position: -16px 0; }
  95 +.ui-icon-carat-1-e { background-position: -32px 0; }
  96 +.ui-icon-carat-1-se { background-position: -48px 0; }
  97 +.ui-icon-carat-1-s { background-position: -64px 0; }
  98 +.ui-icon-carat-1-sw { background-position: -80px 0; }
  99 +.ui-icon-carat-1-w { background-position: -96px 0; }
  100 +.ui-icon-carat-1-nw { background-position: -112px 0; }
  101 +.ui-icon-carat-2-n-s { background-position: -128px 0; }
  102 +.ui-icon-carat-2-e-w { background-position: -144px 0; }
  103 +.ui-icon-triangle-1-n { background-position: 0 -16px; }
  104 +.ui-icon-triangle-1-ne { background-position: -16px -16px; }
  105 +.ui-icon-triangle-1-e { background-position: -32px -16px; }
  106 +.ui-icon-triangle-1-se { background-position: -48px -16px; }
  107 +.ui-icon-triangle-1-s { background-position: -64px -16px; }
  108 +.ui-icon-triangle-1-sw { background-position: -80px -16px; }
  109 +.ui-icon-triangle-1-w { background-position: -96px -16px; }
  110 +.ui-icon-triangle-1-nw { background-position: -112px -16px; }
  111 +.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
  112 +.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
  113 +.ui-icon-arrow-1-n { background-position: 0 -32px; }
  114 +.ui-icon-arrow-1-ne { background-position: -16px -32px; }
  115 +.ui-icon-arrow-1-e { background-position: -32px -32px; }
  116 +.ui-icon-arrow-1-se { background-position: -48px -32px; }
  117 +.ui-icon-arrow-1-s { background-position: -64px -32px; }
  118 +.ui-icon-arrow-1-sw { background-position: -80px -32px; }
  119 +.ui-icon-arrow-1-w { background-position: -96px -32px; }
  120 +.ui-icon-arrow-1-nw { background-position: -112px -32px; }
  121 +.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
  122 +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
  123 +.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
  124 +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
  125 +.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
  126 +.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
  127 +.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
  128 +.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
  129 +.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
  130 +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
  131 +.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
  132 +.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
  133 +.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
  134 +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
  135 +.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
  136 +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
  137 +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
  138 +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
  139 +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
  140 +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
  141 +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
  142 +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
  143 +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
  144 +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
  145 +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
  146 +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
  147 +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
  148 +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
  149 +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
  150 +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
  151 +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
  152 +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
  153 +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
  154 +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
  155 +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
  156 +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
  157 +.ui-icon-arrow-4 { background-position: 0 -80px; }
  158 +.ui-icon-arrow-4-diag { background-position: -16px -80px; }
  159 +.ui-icon-extlink { background-position: -32px -80px; }
  160 +.ui-icon-newwin { background-position: -48px -80px; }
  161 +.ui-icon-refresh { background-position: -64px -80px; }
  162 +.ui-icon-shuffle { background-position: -80px -80px; }
  163 +.ui-icon-transfer-e-w { background-position: -96px -80px; }
  164 +.ui-icon-transferthick-e-w { background-position: -112px -80px; }
  165 +.ui-icon-folder-collapsed { background-position: 0 -96px; }
  166 +.ui-icon-folder-open { background-position: -16px -96px; }
  167 +.ui-icon-document { background-position: -32px -96px; }
  168 +.ui-icon-document-b { background-position: -48px -96px; }
  169 +.ui-icon-note { background-position: -64px -96px; }
  170 +.ui-icon-mail-closed { background-position: -80px -96px; }
  171 +.ui-icon-mail-open { background-position: -96px -96px; }
  172 +.ui-icon-suitcase { background-position: -112px -96px; }
  173 +.ui-icon-comment { background-position: -128px -96px; }
  174 +.ui-icon-person { background-position: -144px -96px; }
  175 +.ui-icon-print { background-position: -160px -96px; }
  176 +.ui-icon-trash { background-position: -176px -96px; }
  177 +.ui-icon-locked { background-position: -192px -96px; }
  178 +.ui-icon-unlocked { background-position: -208px -96px; }
  179 +.ui-icon-bookmark { background-position: -224px -96px; }
  180 +.ui-icon-tag { background-position: -240px -96px; }
  181 +.ui-icon-home { background-position: 0 -112px; }
  182 +.ui-icon-flag { background-position: -16px -112px; }
  183 +.ui-icon-calendar { background-position: -32px -112px; }
  184 +.ui-icon-cart { background-position: -48px -112px; }
  185 +.ui-icon-pencil { background-position: -64px -112px; }
  186 +.ui-icon-clock { background-position: -80px -112px; }
  187 +.ui-icon-disk { background-position: -96px -112px; }
  188 +.ui-icon-calculator { background-position: -112px -112px; }
  189 +.ui-icon-zoomin { background-position: -128px -112px; }
  190 +.ui-icon-zoomout { background-position: -144px -112px; }
  191 +.ui-icon-search { background-position: -160px -112px; }
  192 +.ui-icon-wrench { background-position: -176px -112px; }
  193 +.ui-icon-gear { background-position: -192px -112px; }
  194 +.ui-icon-heart { background-position: -208px -112px; }
  195 +.ui-icon-star { background-position: -224px -112px; }
  196 +.ui-icon-link { background-position: -240px -112px; }
  197 +.ui-icon-cancel { background-position: 0 -128px; }
  198 +.ui-icon-plus { background-position: -16px -128px; }
  199 +.ui-icon-plusthick { background-position: -32px -128px; }
  200 +.ui-icon-minus { background-position: -48px -128px; }
  201 +.ui-icon-minusthick { background-position: -64px -128px; }
  202 +.ui-icon-close { background-position: -80px -128px; }
  203 +.ui-icon-closethick { background-position: -96px -128px; }
  204 +.ui-icon-key { background-position: -112px -128px; }
  205 +.ui-icon-lightbulb { background-position: -128px -128px; }
  206 +.ui-icon-scissors { background-position: -144px -128px; }
  207 +.ui-icon-clipboard { background-position: -160px -128px; }
  208 +.ui-icon-copy { background-position: -176px -128px; }
  209 +.ui-icon-contact { background-position: -192px -128px; }
  210 +.ui-icon-image { background-position: -208px -128px; }
  211 +.ui-icon-video { background-position: -224px -128px; }
  212 +.ui-icon-script { background-position: -240px -128px; }
  213 +.ui-icon-alert { background-position: 0 -144px; }
  214 +.ui-icon-info { background-position: -16px -144px; }
  215 +.ui-icon-notice { background-position: -32px -144px; }
  216 +.ui-icon-help { background-position: -48px -144px; }
  217 +.ui-icon-check { background-position: -64px -144px; }
  218 +.ui-icon-bullet { background-position: -80px -144px; }
  219 +.ui-icon-radio-off { background-position: -96px -144px; }
  220 +.ui-icon-radio-on { background-position: -112px -144px; }
  221 +.ui-icon-pin-w { background-position: -128px -144px; }
  222 +.ui-icon-pin-s { background-position: -144px -144px; }
  223 +.ui-icon-play { background-position: 0 -160px; }
  224 +.ui-icon-pause { background-position: -16px -160px; }
  225 +.ui-icon-seek-next { background-position: -32px -160px; }
  226 +.ui-icon-seek-prev { background-position: -48px -160px; }
  227 +.ui-icon-seek-end { background-position: -64px -160px; }
  228 +.ui-icon-seek-start { background-position: -80px -160px; }
  229 +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
  230 +.ui-icon-seek-first { background-position: -80px -160px; }
  231 +.ui-icon-stop { background-position: -96px -160px; }
  232 +.ui-icon-eject { background-position: -112px -160px; }
  233 +.ui-icon-volume-off { background-position: -128px -160px; }
  234 +.ui-icon-volume-on { background-position: -144px -160px; }
  235 +.ui-icon-power { background-position: 0 -176px; }
  236 +.ui-icon-signal-diag { background-position: -16px -176px; }
  237 +.ui-icon-signal { background-position: -32px -176px; }
  238 +.ui-icon-battery-0 { background-position: -48px -176px; }
  239 +.ui-icon-battery-1 { background-position: -64px -176px; }
  240 +.ui-icon-battery-2 { background-position: -80px -176px; }
  241 +.ui-icon-battery-3 { background-position: -96px -176px; }
  242 +.ui-icon-circle-plus { background-position: 0 -192px; }
  243 +.ui-icon-circle-minus { background-position: -16px -192px; }
  244 +.ui-icon-circle-close { background-position: -32px -192px; }
  245 +.ui-icon-circle-triangle-e { background-position: -48px -192px; }
  246 +.ui-icon-circle-triangle-s { background-position: -64px -192px; }
  247 +.ui-icon-circle-triangle-w { background-position: -80px -192px; }
  248 +.ui-icon-circle-triangle-n { background-position: -96px -192px; }
  249 +.ui-icon-circle-arrow-e { background-position: -112px -192px; }
  250 +.ui-icon-circle-arrow-s { background-position: -128px -192px; }
  251 +.ui-icon-circle-arrow-w { background-position: -144px -192px; }
  252 +.ui-icon-circle-arrow-n { background-position: -160px -192px; }
  253 +.ui-icon-circle-zoomin { background-position: -176px -192px; }
  254 +.ui-icon-circle-zoomout { background-position: -192px -192px; }
  255 +.ui-icon-circle-check { background-position: -208px -192px; }
  256 +.ui-icon-circlesmall-plus { background-position: 0 -208px; }
  257 +.ui-icon-circlesmall-minus { background-position: -16px -208px; }
  258 +.ui-icon-circlesmall-close { background-position: -32px -208px; }
  259 +.ui-icon-squaresmall-plus { background-position: -48px -208px; }
  260 +.ui-icon-squaresmall-minus { background-position: -64px -208px; }
  261 +.ui-icon-squaresmall-close { background-position: -80px -208px; }
  262 +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
  263 +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
  264 +.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
  265 +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
  266 +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
  267 +.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
  268 +
  269 +
  270 +/* Misc visuals
  271 +----------------------------------*/
  272 +
  273 +/* Corner radius */
  274 +.ui-corner-tl { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; }
  275 +.ui-corner-tr { -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; }
  276 +.ui-corner-bl { -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; }
  277 +.ui-corner-br { -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; }
  278 +.ui-corner-top { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; }
  279 +.ui-corner-bottom { -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; }
  280 +.ui-corner-right { -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; }
  281 +.ui-corner-left { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; }
  282 +.ui-corner-all { -moz-border-radius: 6px; -webkit-border-radius: 6px; border-radius: 6px; }
  283 +
  284 +/* Overlays */
  285 +.ui-widget-overlay { background: #2b2922 url(images/ui-bg_inset-soft_15_2b2922_1x100.png) 50% bottom repeat-x; opacity: .90;filter:Alpha(Opacity=90); }
  286 +.ui-widget-shadow { margin: -12px 0 0 -12px; padding: 12px; background: #cccccc url(images/ui-bg_highlight-hard_95_cccccc_1x100.png) 50% top repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; }/* Resizable
  287 +----------------------------------*/
  288 +.ui-resizable { position: relative;}
  289 +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
  290 +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
  291 +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
  292 +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
  293 +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
  294 +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
  295 +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
  296 +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
  297 +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
  298 +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Selectable
  299 +----------------------------------*/
  300 +.ui-selectable-helper { border:1px dotted black }
  301 +/* Accordion
  302 +----------------------------------*/
  303 +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
  304 +.ui-accordion .ui-accordion-li-fix { display: inline; }
  305 +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
  306 +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
  307 +/* IE7-/Win - Fix extra vertical space in lists */
  308 +.ui-accordion a { zoom: 1; }
  309 +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
  310 +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
  311 +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
  312 +.ui-accordion .ui-accordion-content-active { display: block; }/* Autocomplete
  313 +----------------------------------*/
  314 +.ui-autocomplete { position: absolute; cursor: default; }
  315 +.ui-autocomplete-loading { background: white url('images/ui-anim_basic_16x16.gif') right center no-repeat; }
  316 +
  317 +/* workarounds */
  318 +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
  319 +
  320 +/* Menu
  321 +----------------------------------*/
  322 +.ui-menu {
  323 + list-style:none;
  324 + padding: 2px;
  325 + margin: 0;
  326 + display:block;
  327 +}
  328 +.ui-menu .ui-menu {
  329 + margin-top: -3px;
  330 +}
  331 +.ui-menu .ui-menu-item {
  332 + margin:0;
  333 + padding: 0;
  334 + zoom: 1;
  335 + float: left;
  336 + clear: left;
  337 + width: 100%;
  338 +}
  339 +.ui-menu .ui-menu-item a {
  340 + text-decoration:none;
  341 + display:block;
  342 + padding:.2em .4em;
  343 + line-height:1.5;
  344 + zoom:1;
  345 +}
  346 +.ui-menu .ui-menu-item a.ui-state-hover,
  347 +.ui-menu .ui-menu-item a.ui-state-active {
  348 + font-weight: normal;
  349 + margin: -1px;
  350 +}
  351 +/* Button
  352 +----------------------------------*/
  353 +
  354 +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
  355 +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
  356 +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
  357 +.ui-button-icons-only { width: 3.4em; }
  358 +button.ui-button-icons-only { width: 3.7em; }
  359 +
  360 +/*button text element */
  361 +.ui-button .ui-button-text { display: block; line-height: 1.4; }
  362 +.ui-button-text-only .ui-button-text { padding: .4em 1em; }
  363 +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
  364 +.ui-button-text-icon .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
  365 +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
  366 +/* no icon support for input elements, provide padding by default */
  367 +input.ui-button { padding: .4em 1em; }
  368 +
  369 +/*button icon element(s) */
  370 +.ui-button-icon-only .ui-icon, .ui-button-text-icon .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
  371 +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
  372 +.ui-button-text-icon .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
  373 +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
  374 +
  375 +/*button sets*/
  376 +.ui-buttonset { margin-right: 7px; }
  377 +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
  378 +
  379 +/* workarounds */
  380 +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
  381 +
  382 +
  383 +
  384 +
  385 +
  386 +/* Dialog
  387 +----------------------------------*/
  388 +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
  389 +.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; }
  390 +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; }
  391 +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
  392 +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
  393 +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
  394 +.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
  395 +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
  396 +.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; }
  397 +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
  398 +.ui-draggable .ui-dialog-titlebar { cursor: move; }
  399 +/* Slider
  400 +----------------------------------*/
  401 +.ui-slider { position: relative; text-align: left; }
  402 +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
  403 +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
  404 +
  405 +.ui-slider-horizontal { height: .8em; }
  406 +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
  407 +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
  408 +.ui-slider-horizontal .ui-slider-range-min { left: 0; }
  409 +.ui-slider-horizontal .ui-slider-range-max { right: 0; }
  410 +
  411 +.ui-slider-vertical { width: .8em; height: 100px; }
  412 +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
  413 +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
  414 +.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
  415 +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs
  416 +----------------------------------*/
  417 +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
  418 +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
  419 +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
  420 +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
  421 +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
  422 +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
  423 +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
  424 +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
  425 +.ui-tabs .ui-tabs-hide { display: none !important; }
  426 +/* Datepicker
  427 +----------------------------------*/
  428 +.ui-datepicker { width: 17em; padding: .2em .2em 0; }
  429 +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
  430 +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
  431 +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
  432 +.ui-datepicker .ui-datepicker-prev { left:2px; }
  433 +.ui-datepicker .ui-datepicker-next { right:2px; }
  434 +.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
  435 +.ui-datepicker .ui-datepicker-next-hover { right:1px; }
  436 +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
  437 +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
  438 +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
  439 +.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
  440 +.ui-datepicker select.ui-datepicker-month,
  441 +.ui-datepicker select.ui-datepicker-year { width: 49%;}
  442 +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
  443 +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
  444 +.ui-datepicker td { border: 0; padding: 1px; }
  445 +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
  446 +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
  447 +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
  448 +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
  449 +
  450 +/* with multiple calendars */
  451 +.ui-datepicker.ui-datepicker-multi { width:auto; }
  452 +.ui-datepicker-multi .ui-datepicker-group { float:left; }
  453 +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
  454 +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
  455 +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
  456 +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
  457 +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
  458 +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
  459 +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
  460 +.ui-datepicker-row-break { clear:both; width:100%; }
  461 +
  462 +/* RTL support */
  463 +.ui-datepicker-rtl { direction: rtl; }
  464 +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
  465 +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
  466 +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
  467 +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
  468 +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
  469 +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
  470 +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
  471 +.ui-datepicker-rtl .ui-datepicker-group { float:right; }
  472 +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
  473 +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
  474 +
  475 +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
  476 +.ui-datepicker-cover {
  477 + display: none; /*sorry for IE5*/
  478 + display/**/: block; /*sorry for IE5*/
  479 + position: absolute; /*must have*/
  480 + z-index: -1; /*must have*/
  481 + filter: mask(); /*must have*/
  482 + top: -4px; /*must have*/
  483 + left: -4px; /*must have*/
  484 + width: 200px; /*must have*/
  485 + height: 200px; /*must have*/
  486 +}/* Progressbar
  487 +----------------------------------*/
  488 +.ui-progressbar { height:2em; text-align: left; }
  489 +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
0 490 \ No newline at end of file
... ...
public/stylesheets/jquery.ui/sunny/images/ui-anim_basic_16x16.gif 0 → 100644

1.52 KB

public/stylesheets/jquery.ui/sunny/images/ui-bg_diagonals-medium_20_d34d17_40x40.png 0 → 100644

199 Bytes

public/stylesheets/jquery.ui/sunny/images/ui-bg_flat_30_cccccc_40x100.png 0 → 100644

180 Bytes

public/stylesheets/jquery.ui/sunny/images/ui-bg_flat_50_5c5c5c_40x100.png 0 → 100644

180 Bytes

public/stylesheets/jquery.ui/sunny/images/ui-bg_gloss-wave_45_817865_500x100.png 0 → 100644

3.88 KB

public/stylesheets/jquery.ui/sunny/images/ui-bg_gloss-wave_60_fece2f_500x100.png 0 → 100644

3.43 KB

public/stylesheets/jquery.ui/sunny/images/ui-bg_gloss-wave_70_ffdd57_500x100.png 0 → 100644

3.05 KB

public/stylesheets/jquery.ui/sunny/images/ui-bg_gloss-wave_90_fff9e5_500x100.png 0 → 100644

2.2 KB

public/stylesheets/jquery.ui/sunny/images/ui-bg_highlight-soft_100_feeebd_1x100.png 0 → 100644

108 Bytes

public/stylesheets/jquery.ui/sunny/images/ui-bg_inset-soft_30_ffffff_1x100.png 0 → 100644

100 Bytes

public/stylesheets/jquery.ui/sunny/images/ui-icons_3d3d3d_256x240.png 0 → 100644

4.27 KB

public/stylesheets/jquery.ui/sunny/images/ui-icons_bd7b00_256x240.png 0 → 100644

5.23 KB

public/stylesheets/jquery.ui/sunny/images/ui-icons_d19405_256x240.png 0 → 100644

4.27 KB

public/stylesheets/jquery.ui/sunny/images/ui-icons_eb990f_256x240.png 0 → 100644

4.27 KB

public/stylesheets/jquery.ui/sunny/images/ui-icons_ed9f26_256x240.png 0 → 100644

4.27 KB

public/stylesheets/jquery.ui/sunny/images/ui-icons_fadc7a_256x240.png 0 → 100644

4.27 KB

public/stylesheets/jquery.ui/sunny/images/ui-icons_ffe180_256x240.png 0 → 100644

5.23 KB

public/stylesheets/jquery.ui/sunny/jquery-ui-1.8.2.custom.css 0 → 100644
... ... @@ -0,0 +1,489 @@
  1 +/*
  2 +* jQuery UI CSS Framework
  3 +* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
  4 +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
  5 +*/
  6 +
  7 +/* Layout helpers
  8 +----------------------------------*/
  9 +.ui-helper-hidden { display: none; }
  10 +.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
  11 +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
  12 +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
  13 +.ui-helper-clearfix { display: inline-block; }
  14 +/* required comment for clearfix to work in Opera \*/
  15 +* html .ui-helper-clearfix { height:1%; }
  16 +.ui-helper-clearfix { display:block; }
  17 +/* end clearfix */
  18 +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
  19 +
  20 +
  21 +/* Interaction Cues
  22 +----------------------------------*/
  23 +.ui-state-disabled { cursor: default !important; }
  24 +
  25 +
  26 +/* Icons
  27 +----------------------------------*/
  28 +
  29 +/* states and images */
  30 +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
  31 +
  32 +
  33 +/* Misc visuals
  34 +----------------------------------*/
  35 +
  36 +/* Overlays */
  37 +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
  38 +
  39 +
  40 +/*
  41 +* jQuery UI CSS Framework
  42 +* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
  43 +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
  44 +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Segoe%20UI,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=8px&bgColorHeader=817865&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=45&borderColorHeader=494437&fcHeader=ffffff&iconColorHeader=fadc7a&bgColorContent=feeebd&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=8e846b&fcContent=383838&iconColorContent=d19405&bgColorDefault=fece2f&bgTextureDefault=12_gloss_wave.png&bgImgOpacityDefault=60&borderColorDefault=d19405&fcDefault=4c3000&iconColorDefault=3d3d3d&bgColorHover=ffdd57&bgTextureHover=12_gloss_wave.png&bgImgOpacityHover=70&borderColorHover=a45b13&fcHover=381f00&iconColorHover=bd7b00&bgColorActive=ffffff&bgTextureActive=05_inset_soft.png&bgImgOpacityActive=30&borderColorActive=655e4e&fcActive=0074c7&iconColorActive=eb990f&bgColorHighlight=fff9e5&bgTextureHighlight=12_gloss_wave.png&bgImgOpacityHighlight=90&borderColorHighlight=eeb420&fcHighlight=1f1f1f&iconColorHighlight=ed9f26&bgColorError=d34d17&bgTextureError=07_diagonals_medium.png&bgImgOpacityError=20&borderColorError=ffb73d&fcError=ffffff&iconColorError=ffe180&bgColorOverlay=5c5c5c&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=50&opacityOverlay=80&bgColorShadow=cccccc&bgTextureShadow=01_flat.png&bgImgOpacityShadow=30&opacityShadow=60&thicknessShadow=7px&offsetTopShadow=-7px&offsetLeftShadow=-7px&cornerRadiusShadow=8px
  45 +*/
  46 +
  47 +
  48 +/* Component containers
  49 +----------------------------------*/
  50 +.ui-widget { font-family: Segoe UI, Arial, sans-serif; font-size: 1.1em; }
  51 +.ui-widget .ui-widget { font-size: 1em; }
  52 +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Segoe UI, Arial, sans-serif; font-size: 1em; }
  53 +.ui-widget-content { border: 1px solid #8e846b; background: #feeebd url(images/ui-bg_highlight-soft_100_feeebd_1x100.png) 50% top repeat-x; color: #383838; }
  54 +.ui-widget-content a { color: #383838; }
  55 +.ui-widget-header { border: 1px solid #494437; background: #817865 url(images/ui-bg_gloss-wave_45_817865_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
  56 +.ui-widget-header a { color: #ffffff; }
  57 +
  58 +/* Interaction states
  59 +----------------------------------*/
  60 +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d19405; background: #fece2f url(images/ui-bg_gloss-wave_60_fece2f_500x100.png) 50% 50% repeat-x; font-weight: bold; color: #4c3000; }
  61 +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #4c3000; text-decoration: none; }
  62 +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #a45b13; background: #ffdd57 url(images/ui-bg_gloss-wave_70_ffdd57_500x100.png) 50% 50% repeat-x; font-weight: bold; color: #381f00; }
  63 +.ui-state-hover a, .ui-state-hover a:hover { color: #381f00; text-decoration: none; }
  64 +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #655e4e; background: #ffffff url(images/ui-bg_inset-soft_30_ffffff_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #0074c7; }
  65 +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #0074c7; text-decoration: none; }
  66 +.ui-widget :active { outline: none; }
  67 +
  68 +/* Interaction Cues
  69 +----------------------------------*/
  70 +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #eeb420; background: #fff9e5 url(images/ui-bg_gloss-wave_90_fff9e5_500x100.png) 50% top repeat-x; color: #1f1f1f; }
  71 +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #1f1f1f; }
  72 +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #ffb73d; background: #d34d17 url(images/ui-bg_diagonals-medium_20_d34d17_40x40.png) 50% 50% repeat; color: #ffffff; }
  73 +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
  74 +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
  75 +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
  76 +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
  77 +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
  78 +
  79 +/* Icons
  80 +----------------------------------*/
  81 +
  82 +/* states and images */
  83 +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_d19405_256x240.png); }
  84 +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_d19405_256x240.png); }
  85 +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_fadc7a_256x240.png); }
  86 +.ui-state-default .ui-icon { background-image: url(images/ui-icons_3d3d3d_256x240.png); }
  87 +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_bd7b00_256x240.png); }
  88 +.ui-state-active .ui-icon {background-image: url(images/ui-icons_eb990f_256x240.png); }
  89 +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_ed9f26_256x240.png); }
  90 +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffe180_256x240.png); }
  91 +
  92 +/* positioning */
  93 +.ui-icon-carat-1-n { background-position: 0 0; }
  94 +.ui-icon-carat-1-ne { background-position: -16px 0; }
  95 +.ui-icon-carat-1-e { background-position: -32px 0; }
  96 +.ui-icon-carat-1-se { background-position: -48px 0; }
  97 +.ui-icon-carat-1-s { background-position: -64px 0; }
  98 +.ui-icon-carat-1-sw { background-position: -80px 0; }
  99 +.ui-icon-carat-1-w { background-position: -96px 0; }
  100 +.ui-icon-carat-1-nw { background-position: -112px 0; }
  101 +.ui-icon-carat-2-n-s { background-position: -128px 0; }
  102 +.ui-icon-carat-2-e-w { background-position: -144px 0; }
  103 +.ui-icon-triangle-1-n { background-position: 0 -16px; }
  104 +.ui-icon-triangle-1-ne { background-position: -16px -16px; }
  105 +.ui-icon-triangle-1-e { background-position: -32px -16px; }
  106 +.ui-icon-triangle-1-se { background-position: -48px -16px; }
  107 +.ui-icon-triangle-1-s { background-position: -64px -16px; }
  108 +.ui-icon-triangle-1-sw { background-position: -80px -16px; }
  109 +.ui-icon-triangle-1-w { background-position: -96px -16px; }
  110 +.ui-icon-triangle-1-nw { background-position: -112px -16px; }
  111 +.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
  112 +.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
  113 +.ui-icon-arrow-1-n { background-position: 0 -32px; }
  114 +.ui-icon-arrow-1-ne { background-position: -16px -32px; }
  115 +.ui-icon-arrow-1-e { background-position: -32px -32px; }
  116 +.ui-icon-arrow-1-se { background-position: -48px -32px; }
  117 +.ui-icon-arrow-1-s { background-position: -64px -32px; }
  118 +.ui-icon-arrow-1-sw { background-position: -80px -32px; }
  119 +.ui-icon-arrow-1-w { background-position: -96px -32px; }
  120 +.ui-icon-arrow-1-nw { background-position: -112px -32px; }
  121 +.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
  122 +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
  123 +.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
  124 +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
  125 +.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
  126 +.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
  127 +.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
  128 +.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
  129 +.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
  130 +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
  131 +.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
  132 +.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
  133 +.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
  134 +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
  135 +.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
  136 +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
  137 +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
  138 +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
  139 +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
  140 +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
  141 +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
  142 +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
  143 +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
  144 +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
  145 +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
  146 +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
  147 +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
  148 +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
  149 +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
  150 +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
  151 +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
  152 +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
  153 +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
  154 +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
  155 +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
  156 +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
  157 +.ui-icon-arrow-4 { background-position: 0 -80px; }
  158 +.ui-icon-arrow-4-diag { background-position: -16px -80px; }
  159 +.ui-icon-extlink { background-position: -32px -80px; }
  160 +.ui-icon-newwin { background-position: -48px -80px; }
  161 +.ui-icon-refresh { background-position: -64px -80px; }
  162 +.ui-icon-shuffle { background-position: -80px -80px; }
  163 +.ui-icon-transfer-e-w { background-position: -96px -80px; }
  164 +.ui-icon-transferthick-e-w { background-position: -112px -80px; }
  165 +.ui-icon-folder-collapsed { background-position: 0 -96px; }
  166 +.ui-icon-folder-open { background-position: -16px -96px; }
  167 +.ui-icon-document { background-position: -32px -96px; }
  168 +.ui-icon-document-b { background-position: -48px -96px; }
  169 +.ui-icon-note { background-position: -64px -96px; }
  170 +.ui-icon-mail-closed { background-position: -80px -96px; }
  171 +.ui-icon-mail-open { background-position: -96px -96px; }
  172 +.ui-icon-suitcase { background-position: -112px -96px; }
  173 +.ui-icon-comment { background-position: -128px -96px; }
  174 +.ui-icon-person { background-position: -144px -96px; }
  175 +.ui-icon-print { background-position: -160px -96px; }
  176 +.ui-icon-trash { background-position: -176px -96px; }
  177 +.ui-icon-locked { background-position: -192px -96px; }
  178 +.ui-icon-unlocked { background-position: -208px -96px; }
  179 +.ui-icon-bookmark { background-position: -224px -96px; }
  180 +.ui-icon-tag { background-position: -240px -96px; }
  181 +.ui-icon-home { background-position: 0 -112px; }
  182 +.ui-icon-flag { background-position: -16px -112px; }
  183 +.ui-icon-calendar { background-position: -32px -112px; }
  184 +.ui-icon-cart { background-position: -48px -112px; }
  185 +.ui-icon-pencil { background-position: -64px -112px; }
  186 +.ui-icon-clock { background-position: -80px -112px; }
  187 +.ui-icon-disk { background-position: -96px -112px; }
  188 +.ui-icon-calculator { background-position: -112px -112px; }
  189 +.ui-icon-zoomin { background-position: -128px -112px; }
  190 +.ui-icon-zoomout { background-position: -144px -112px; }
  191 +.ui-icon-search { background-position: -160px -112px; }
  192 +.ui-icon-wrench { background-position: -176px -112px; }
  193 +.ui-icon-gear { background-position: -192px -112px; }
  194 +.ui-icon-heart { background-position: -208px -112px; }
  195 +.ui-icon-star { background-position: -224px -112px; }
  196 +.ui-icon-link { background-position: -240px -112px; }
  197 +.ui-icon-cancel { background-position: 0 -128px; }
  198 +.ui-icon-plus { background-position: -16px -128px; }
  199 +.ui-icon-plusthick { background-position: -32px -128px; }
  200 +.ui-icon-minus { background-position: -48px -128px; }
  201 +.ui-icon-minusthick { background-position: -64px -128px; }
  202 +.ui-icon-close { background-position: -80px -128px; }
  203 +.ui-icon-closethick { background-position: -96px -128px; }
  204 +.ui-icon-key { background-position: -112px -128px; }
  205 +.ui-icon-lightbulb { background-position: -128px -128px; }
  206 +.ui-icon-scissors { background-position: -144px -128px; }
  207 +.ui-icon-clipboard { background-position: -160px -128px; }
  208 +.ui-icon-copy { background-position: -176px -128px; }
  209 +.ui-icon-contact { background-position: -192px -128px; }
  210 +.ui-icon-image { background-position: -208px -128px; }
  211 +.ui-icon-video { background-position: -224px -128px; }
  212 +.ui-icon-script { background-position: -240px -128px; }
  213 +.ui-icon-alert { background-position: 0 -144px; }
  214 +.ui-icon-info { background-position: -16px -144px; }
  215 +.ui-icon-notice { background-position: -32px -144px; }
  216 +.ui-icon-help { background-position: -48px -144px; }
  217 +.ui-icon-check { background-position: -64px -144px; }
  218 +.ui-icon-bullet { background-position: -80px -144px; }
  219 +.ui-icon-radio-off { background-position: -96px -144px; }
  220 +.ui-icon-radio-on { background-position: -112px -144px; }
  221 +.ui-icon-pin-w { background-position: -128px -144px; }
  222 +.ui-icon-pin-s { background-position: -144px -144px; }
  223 +.ui-icon-play { background-position: 0 -160px; }
  224 +.ui-icon-pause { background-position: -16px -160px; }
  225 +.ui-icon-seek-next { background-position: -32px -160px; }
  226 +.ui-icon-seek-prev { background-position: -48px -160px; }
  227 +.ui-icon-seek-end { background-position: -64px -160px; }
  228 +.ui-icon-seek-start { background-position: -80px -160px; }
  229 +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
  230 +.ui-icon-seek-first { background-position: -80px -160px; }
  231 +.ui-icon-stop { background-position: -96px -160px; }
  232 +.ui-icon-eject { background-position: -112px -160px; }
  233 +.ui-icon-volume-off { background-position: -128px -160px; }
  234 +.ui-icon-volume-on { background-position: -144px -160px; }
  235 +.ui-icon-power { background-position: 0 -176px; }
  236 +.ui-icon-signal-diag { background-position: -16px -176px; }
  237 +.ui-icon-signal { background-position: -32px -176px; }
  238 +.ui-icon-battery-0 { background-position: -48px -176px; }
  239 +.ui-icon-battery-1 { background-position: -64px -176px; }
  240 +.ui-icon-battery-2 { background-position: -80px -176px; }
  241 +.ui-icon-battery-3 { background-position: -96px -176px; }
  242 +.ui-icon-circle-plus { background-position: 0 -192px; }
  243 +.ui-icon-circle-minus { background-position: -16px -192px; }
  244 +.ui-icon-circle-close { background-position: -32px -192px; }
  245 +.ui-icon-circle-triangle-e { background-position: -48px -192px; }
  246 +.ui-icon-circle-triangle-s { background-position: -64px -192px; }
  247 +.ui-icon-circle-triangle-w { background-position: -80px -192px; }
  248 +.ui-icon-circle-triangle-n { background-position: -96px -192px; }
  249 +.ui-icon-circle-arrow-e { background-position: -112px -192px; }
  250 +.ui-icon-circle-arrow-s { background-position: -128px -192px; }
  251 +.ui-icon-circle-arrow-w { background-position: -144px -192px; }
  252 +.ui-icon-circle-arrow-n { background-position: -160px -192px; }
  253 +.ui-icon-circle-zoomin { background-position: -176px -192px; }
  254 +.ui-icon-circle-zoomout { background-position: -192px -192px; }
  255 +.ui-icon-circle-check { background-position: -208px -192px; }
  256 +.ui-icon-circlesmall-plus { background-position: 0 -208px; }
  257 +.ui-icon-circlesmall-minus { background-position: -16px -208px; }
  258 +.ui-icon-circlesmall-close { background-position: -32px -208px; }
  259 +.ui-icon-squaresmall-plus { background-position: -48px -208px; }
  260 +.ui-icon-squaresmall-minus { background-position: -64px -208px; }
  261 +.ui-icon-squaresmall-close { background-position: -80px -208px; }
  262 +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
  263 +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
  264 +.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
  265 +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
  266 +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
  267 +.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
  268 +
  269 +
  270 +/* Misc visuals
  271 +----------------------------------*/
  272 +
  273 +/* Corner radius */
  274 +.ui-corner-tl { -moz-border-radius-topleft: 8px; -webkit-border-top-left-radius: 8px; border-top-left-radius: 8px; }
  275 +.ui-corner-tr { -moz-border-radius-topright: 8px; -webkit-border-top-right-radius: 8px; border-top-right-radius: 8px; }
  276 +.ui-corner-bl { -moz-border-radius-bottomleft: 8px; -webkit-border-bottom-left-radius: 8px; border-bottom-left-radius: 8px; }
  277 +.ui-corner-br { -moz-border-radius-bottomright: 8px; -webkit-border-bottom-right-radius: 8px; border-bottom-right-radius: 8px; }
  278 +.ui-corner-top { -moz-border-radius-topleft: 8px; -webkit-border-top-left-radius: 8px; border-top-left-radius: 8px; -moz-border-radius-topright: 8px; -webkit-border-top-right-radius: 8px; border-top-right-radius: 8px; }
  279 +.ui-corner-bottom { -moz-border-radius-bottomleft: 8px; -webkit-border-bottom-left-radius: 8px; border-bottom-left-radius: 8px; -moz-border-radius-bottomright: 8px; -webkit-border-bottom-right-radius: 8px; border-bottom-right-radius: 8px; }
  280 +.ui-corner-right { -moz-border-radius-topright: 8px; -webkit-border-top-right-radius: 8px; border-top-right-radius: 8px; -moz-border-radius-bottomright: 8px; -webkit-border-bottom-right-radius: 8px; border-bottom-right-radius: 8px; }
  281 +.ui-corner-left { -moz-border-radius-topleft: 8px; -webkit-border-top-left-radius: 8px; border-top-left-radius: 8px; -moz-border-radius-bottomleft: 8px; -webkit-border-bottom-left-radius: 8px; border-bottom-left-radius: 8px; }
  282 +.ui-corner-all { -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }
  283 +
  284 +/* Overlays */
  285 +.ui-widget-overlay { background: #5c5c5c url(images/ui-bg_flat_50_5c5c5c_40x100.png) 50% 50% repeat-x; opacity: .80;filter:Alpha(Opacity=80); }
  286 +.ui-widget-shadow { margin: -7px 0 0 -7px; padding: 7px; background: #cccccc url(images/ui-bg_flat_30_cccccc_40x100.png) 50% 50% repeat-x; opacity: .60;filter:Alpha(Opacity=60); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/* Resizable
  287 +----------------------------------*/
  288 +.ui-resizable { position: relative;}
  289 +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
  290 +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
  291 +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
  292 +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
  293 +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
  294 +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
  295 +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
  296 +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
  297 +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
  298 +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Selectable
  299 +----------------------------------*/
  300 +.ui-selectable-helper { border:1px dotted black }
  301 +/* Accordion
  302 +----------------------------------*/
  303 +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
  304 +.ui-accordion .ui-accordion-li-fix { display: inline; }
  305 +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
  306 +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
  307 +/* IE7-/Win - Fix extra vertical space in lists */
  308 +.ui-accordion a { zoom: 1; }
  309 +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
  310 +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
  311 +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
  312 +.ui-accordion .ui-accordion-content-active { display: block; }/* Autocomplete
  313 +----------------------------------*/
  314 +.ui-autocomplete { position: absolute; cursor: default; }
  315 +.ui-autocomplete-loading { background: white url('images/ui-anim_basic_16x16.gif') right center no-repeat; }
  316 +
  317 +/* workarounds */
  318 +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
  319 +
  320 +/* Menu
  321 +----------------------------------*/
  322 +.ui-menu {
  323 + list-style:none;
  324 + padding: 2px;
  325 + margin: 0;
  326 + display:block;
  327 +}
  328 +.ui-menu .ui-menu {
  329 + margin-top: -3px;
  330 +}
  331 +.ui-menu .ui-menu-item {
  332 + margin:0;
  333 + padding: 0;
  334 + zoom: 1;
  335 + float: left;
  336 + clear: left;
  337 + width: 100%;
  338 +}
  339 +.ui-menu .ui-menu-item a {
  340 + text-decoration:none;
  341 + display:block;
  342 + padding:.2em .4em;
  343 + line-height:1.5;
  344 + zoom:1;
  345 +}
  346 +.ui-menu .ui-menu-item a.ui-state-hover,
  347 +.ui-menu .ui-menu-item a.ui-state-active {
  348 + font-weight: normal;
  349 + margin: -1px;
  350 +}
  351 +/* Button
  352 +----------------------------------*/
  353 +
  354 +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
  355 +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
  356 +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
  357 +.ui-button-icons-only { width: 3.4em; }
  358 +button.ui-button-icons-only { width: 3.7em; }
  359 +
  360 +/*button text element */
  361 +.ui-button .ui-button-text { display: block; line-height: 1.4; }
  362 +.ui-button-text-only .ui-button-text { padding: .4em 1em; }
  363 +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
  364 +.ui-button-text-icon .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
  365 +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
  366 +/* no icon support for input elements, provide padding by default */
  367 +input.ui-button { padding: .4em 1em; }
  368 +
  369 +/*button icon element(s) */
  370 +.ui-button-icon-only .ui-icon, .ui-button-text-icon .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
  371 +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
  372 +.ui-button-text-icon .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
  373 +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
  374 +
  375 +/*button sets*/
  376 +.ui-buttonset { margin-right: 7px; }
  377 +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
  378 +
  379 +/* workarounds */
  380 +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
  381 +
  382 +
  383 +
  384 +
  385 +
  386 +/* Dialog
  387 +----------------------------------*/
  388 +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
  389 +.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; }
  390 +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; }
  391 +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
  392 +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
  393 +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
  394 +.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
  395 +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
  396 +.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; }
  397 +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
  398 +.ui-draggable .ui-dialog-titlebar { cursor: move; }
  399 +/* Slider
  400 +----------------------------------*/
  401 +.ui-slider { position: relative; text-align: left; }
  402 +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
  403 +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
  404 +
  405 +.ui-slider-horizontal { height: .8em; }
  406 +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
  407 +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
  408 +.ui-slider-horizontal .ui-slider-range-min { left: 0; }
  409 +.ui-slider-horizontal .ui-slider-range-max { right: 0; }
  410 +
  411 +.ui-slider-vertical { width: .8em; height: 100px; }
  412 +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
  413 +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
  414 +.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
  415 +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs
  416 +----------------------------------*/
  417 +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
  418 +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
  419 +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
  420 +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
  421 +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
  422 +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
  423 +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
  424 +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
  425 +.ui-tabs .ui-tabs-hide { display: none !important; }
  426 +/* Datepicker
  427 +----------------------------------*/
  428 +.ui-datepicker { width: 17em; padding: .2em .2em 0; }
  429 +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
  430 +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
  431 +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
  432 +.ui-datepicker .ui-datepicker-prev { left:2px; }
  433 +.ui-datepicker .ui-datepicker-next { right:2px; }
  434 +.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
  435 +.ui-datepicker .ui-datepicker-next-hover { right:1px; }
  436 +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
  437 +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
  438 +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
  439 +.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
  440 +.ui-datepicker select.ui-datepicker-month,
  441 +.ui-datepicker select.ui-datepicker-year { width: 49%;}
  442 +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
  443 +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
  444 +.ui-datepicker td { border: 0; padding: 1px; }
  445 +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
  446 +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
  447 +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
  448 +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
  449 +
  450 +/* with multiple calendars */
  451 +.ui-datepicker.ui-datepicker-multi { width:auto; }
  452 +.ui-datepicker-multi .ui-datepicker-group { float:left; }
  453 +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
  454 +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
  455 +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
  456 +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
  457 +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
  458 +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
  459 +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
  460 +.ui-datepicker-row-break { clear:both; width:100%; }
  461 +
  462 +/* RTL support */
  463 +.ui-datepicker-rtl { direction: rtl; }
  464 +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
  465 +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
  466 +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
  467 +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
  468 +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
  469 +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
  470 +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
  471 +.ui-datepicker-rtl .ui-datepicker-group { float:right; }
  472 +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
  473 +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
  474 +
  475 +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
  476 +.ui-datepicker-cover {
  477 + display: none; /*sorry for IE5*/
  478 + display/**/: block; /*sorry for IE5*/
  479 + position: absolute; /*must have*/
  480 + z-index: -1; /*must have*/
  481 + filter: mask(); /*must have*/
  482 + top: -4px; /*must have*/
  483 + left: -4px; /*must have*/
  484 + width: 200px; /*must have*/
  485 + height: 200px; /*must have*/
  486 +}/* Progressbar
  487 +----------------------------------*/
  488 +.ui-progressbar { height:2em; text-align: left; }
  489 +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
0 490 \ No newline at end of file
... ...
test/functional/catalog_controller_test.rb
... ... @@ -39,31 +39,6 @@ class CatalogControllerTest &lt; Test::Unit::TestCase
39 39 assert_kind_of Array, assigns(:products)
40 40 end
41 41  
42   - should 'show product of enterprise' do
43   - prod = @enterprise.products.create!(:name => 'Product test', :product_category => @product_category)
44   - get :show, :id => prod.id, :profile => @enterprise.identifier
45   - assert_tag :tag => 'h1', :content => /#{prod.name}/
46   - end
47   -
48   - should 'link back to index from product show' do
49   - enterprise = Enterprise.create!(:name => 'test_enterprise_1', :identifier => 'test_enterprise_1', :environment => Environment.default)
50   - prod = enterprise.products.create!(:name => 'Product test', :product_category => @product_category)
51   - get :show, :id => prod.id, :profile => enterprise.identifier
52   - assert_tag({
53   - :tag => 'div',
54   - :attributes => {
55   - :class => /main-block/
56   - },
57   - :descendant => {
58   - :tag => 'a',
59   - :attributes => {
60   - :href => "/catalog/#{enterprise.identifier}"
61   - }
62   - }
63   - })
64   -
65   - end
66   -
67 42 should 'not give access if environment do not let' do
68 43 env = Environment.default
69 44 env.enable('disable_products_for_enterprises')
... ... @@ -86,20 +61,6 @@ class CatalogControllerTest &lt; Test::Unit::TestCase
86 61 assert_tag :tag => 'li', :attributes => { :class => 'product_price' }, :content => /Price:/
87 62 end
88 63  
89   - should 'not show product price when showing product if not informed' do
90   - prod = @enterprise.products.create!(:name => 'Product test', :product_category => @product_category)
91   - get :show, :id => prod.id, :profile => @enterprise.identifier
92   -
93   - assert_no_tag :tag => 'p', :attributes => { :class => 'product_price' }, :content => /Price:/
94   - end
95   -
96   - should 'show product price when showing product if informed' do
97   - prod = @enterprise.products.create!(:name => 'Product test', :price => 50.00, :product_category => @product_category)
98   - get :show, :id => prod.id, :profile => @enterprise.identifier
99   -
100   - assert_tag :tag => 'p', :attributes => { :class => 'product_price' }, :content => /Price:/
101   - end
102   -
103 64 should 'link to assets products wiht product category in the link to product category on index' do
104 65 pc = ProductCategory.create!(:name => 'some product', :environment => enterprise.environment)
105 66 prod = enterprise.products.create!(:name => 'Product test', :price => 50.00, :product_category => pc)
... ... @@ -108,12 +69,4 @@ class CatalogControllerTest &lt; Test::Unit::TestCase
108 69 assert_tag :tag => 'a', :attributes => {:href => /assets\/products\?product_category=#{pc.id}/}
109 70 end
110 71  
111   - should 'link to assets products wiht product category in the link to product category on show' do
112   - pc = ProductCategory.create!(:name => 'some product', :environment => enterprise.environment)
113   - prod = enterprise.products.create!(:name => 'Product test', :price => 50.00, :product_category => pc)
114   -
115   - get :show, :id => prod.id, :profile => enterprise.identifier
116   - assert_tag :tag => 'a', :attributes => {:href => /assets\/products\?product_category=#{pc.id}/}
117   - end
118   -
119 72 end
... ...
test/functional/manage_products_controller_test.rb
... ... @@ -65,27 +65,60 @@ class ManageProductsControllerTest &lt; Test::Unit::TestCase
65 65 end
66 66 end
67 67  
68   - should "get edit form" do
69   - p = @enterprise.products.create!(:name => 'test product', :product_category => @product_category)
70   - get 'edit', :profile => @enterprise.identifier, :id => p.id
  68 + should "get edit name form" do
  69 + product = fast_create(Product, :name => 'test product', :enterprise_id => @enterprise.id, :product_category_id => @product_category.id)
  70 + get 'edit', :profile => @enterprise.identifier, :id => product.id, :field => 'name'
71 71 assert_response :success
72 72 assert assigns(:product)
73   - assert_template 'edit'
74   - assert_tag :tag => 'form', :attributes => { :action => /edit/ }
  73 + assert_tag :tag => 'form', :attributes => { :action => /myprofile\/#{@enterprise.identifier}\/manage_products\/edit\/#{product.id}\?field=name/ }
75 74 end
76 75  
77   - should "edit product" do
78   - p = @enterprise.products.create!(:name => 'test product', :product_category => @product_category)
79   - post 'edit', :profile => @enterprise.identifier, :product => {:name => 'new test product'}, :id => p.id
80   - assert_response :redirect
  76 + should "get edit info form" do
  77 + product = fast_create(Product, :name => 'test product', :enterprise_id => @enterprise.id, :product_category_id => @product_category.id)
  78 + get 'edit', :profile => @enterprise.identifier, :id => product.id, :field => 'info'
  79 + assert_response :success
  80 + assert assigns(:product)
  81 + assert_tag :tag => 'form', :attributes => { :action => /myprofile\/#{@enterprise.identifier}\/manage_products\/edit\/#{product.id}\?field=info/ }
  82 + end
  83 +
  84 + should "get edit image form" do
  85 + product = fast_create(Product, :name => 'test product', :enterprise_id => @enterprise.id, :product_category_id => @product_category.id)
  86 + get 'edit', :profile => @enterprise.identifier, :id => product.id, :field => 'image'
  87 + assert_response :success
  88 + assert assigns(:product)
  89 + assert_tag :tag => 'form', :attributes => { :action => /myprofile\/#{@enterprise.identifier}\/manage_products\/edit\/#{product.id}\?field=image/ }
  90 + end
  91 +
  92 + should "edit product name" do
  93 + product = fast_create(Product, :name => 'test product', :enterprise_id => @enterprise.id, :product_category_id => @product_category.id)
  94 + post :edit, :profile => @enterprise.identifier, :product => {:name => 'new test product'}, :id => product.id, :field => 'name'
  95 + assert_response :success
81 96 assert assigns(:product)
82 97 assert ! assigns(:product).new_record?
83   - assert_equal p, Product.find_by_name('new test product')
  98 + assert_equal product, Product.find_by_name('new test product')
  99 + end
  100 +
  101 + should "edit product description" do
  102 + product = fast_create(Product, :name => 'test product', :enterprise_id => @enterprise.id, :product_category_id => @product_category.id, :description => 'My product is very good')
  103 + post :edit, :profile => @enterprise.identifier, :product => {:description => 'A very good product!'}, :id => product.id, :field => 'info'
  104 + assert_response :success
  105 + assert assigns(:product)
  106 + assert ! assigns(:product).new_record?
  107 + assert_equal 'A very good product!', Product.find_by_name('test product').description
  108 + end
  109 +
  110 + should "edit product image" do
  111 + product = fast_create(Product, :name => 'test product', :enterprise_id => @enterprise.id, :product_category_id => @product_category.id)
  112 + post :edit, :profile => @enterprise.identifier, :product => { :image_builder => { :uploaded_data => fixture_file_upload('/files/rails.png', 'image/png') } }, :id => product.id, :field => 'image'
  113 + assert_response :success
  114 + assert assigns(:product)
  115 + assert ! assigns(:product).new_record?
  116 + assert_equal 'rails.png', Product.find_by_name('test product').image.filename
84 117 end
85 118  
86 119 should "not edit to invalid parameters" do
87   - p = @enterprise.products.create!(:name => 'test product', :product_category => @product_category)
88   - post 'edit', :profile => @enterprise.identifier, :product => {:product_category => nil}, :id => p.id
  120 + product = fast_create(Product, :name => 'test product', :enterprise_id => @enterprise.id, :product_category_id => @product_category.id)
  121 + post 'edit_category', :profile => @enterprise.identifier, :product => {:product_category => nil}, :id => product.id
89 122 assert_response :success
90 123 assert assigns(:product)
91 124 assert ! assigns(:product).valid?
... ... @@ -131,32 +164,16 @@ class ManageProductsControllerTest &lt; Test::Unit::TestCase
131 164 end
132 165 end
133 166  
134   - should 'show thumbnail image when edit product' do
135   - p = @enterprise.products.create!(:name => 'test product1', :product_category => @product_category, :image_builder => {
136   - :uploaded_data => fixture_file_upload('/files/rails.png', 'image/png')
137   - })
138   - get 'edit', :profile => @enterprise.identifier, :id => p.id
139   - assert_tag :tag => 'img', :attributes => { :src => /#{p.image.public_filename(:thumb)}/ }
140   - end
141   -
142   - should 'show change image link above thumbnail image' do
143   - p = @enterprise.products.create!(:name => 'test product1', :product_category => @product_category, :image_builder => {
144   - :uploaded_data => fixture_file_upload('/files/rails.png', 'image/png')
145   - })
146   - get 'edit', :profile => @enterprise.identifier, :id => p.id
147   - assert_tag :tag => 'a', :attributes => { :href => '#' }, :content => 'Change image'
148   - end
149   -
150 167 should 'filter html from name of product' do
151 168 category = fast_create(ProductCategory, :name => 'Category 1')
152 169 post 'new', :profile => @enterprise.identifier, :product => { :name => "<b id='html_name'>name bold</b>", :product_category_id => category.id }
153 170 assert_sanitized assigns(:product).name
154 171 end
155 172  
156   - should 'filter html from description of product' do
  173 + should 'filter html with whit list from description of product' do
157 174 category = fast_create(ProductCategory, :name => 'Category 1')
158 175 post 'new', :profile => @enterprise.identifier, :product => { :name => 'name', :description => "<b id='html_descr'>descr bold</b>", :product_category_id => category.id }
159   - assert_sanitized assigns(:product).description
  176 + assert_equal "<b>descr bold</b>", assigns(:product).description
160 177 end
161 178  
162 179 should 'not let users in if environment do not let' do
... ... @@ -179,15 +196,6 @@ class ManageProductsControllerTest &lt; Test::Unit::TestCase
179 196 assert_equivalent ProductCategory.top_level_for(pc1.environment), assigns(:categories)
180 197 end
181 198  
182   - should 'links to products asset for product category link when showing' do
183   - pc = fast_create(ProductCategory, :name => 'test_category')
184   - p = @enterprise.products.create!(:name => 'test product', :product_category => pc)
185   -
186   - get :show, :profile => @enterprise.identifier, :id => p.id
187   -
188   - assert_tag :tag => 'a', :attributes => {:href => /assets\/products\?product_category=#{pc.id}/}
189   - end
190   -
191 199 should 'increase level while navigate on hierarchy categories' do
192 200 category_level0 = fast_create(ProductCategory, :name => 'Products', :environment_id => @environment.id)
193 201 category_level1 = fast_create(ProductCategory, :parent_id => category_level0.id, :name => 'Shoes', :environment_id => @environment.id)
... ... @@ -232,4 +240,58 @@ class ManageProductsControllerTest &lt; Test::Unit::TestCase
232 240 end
233 241 end
234 242  
  243 + should 'show product of enterprise' do
  244 + prod = @enterprise.products.create!(:name => 'Product test', :product_category => @product_category)
  245 + get :show, :id => prod.id, :profile => @enterprise.identifier
  246 + assert_tag :tag => 'h2', :content => /#{prod.name}/
  247 + end
  248 +
  249 + should 'link back to index from product show' do
  250 + enterprise = Enterprise.create!(:name => 'test_enterprise_1', :identifier => 'test_enterprise_1', :environment => Environment.default)
  251 + prod = enterprise.products.create!(:name => 'Product test', :product_category => @product_category)
  252 + get :show, :id => prod.id, :profile => enterprise.identifier
  253 + assert_tag({
  254 + :tag => 'div',
  255 + :attributes => {
  256 + :class => /main-block/
  257 + },
  258 + :descendant => {
  259 + :tag => 'a',
  260 + :attributes => {
  261 + :href => "/myprofile/#{enterprise.identifier}/manage_products",
  262 + },
  263 + :content => /Back to the product listing/
  264 + }
  265 + })
  266 + end
  267 +
  268 + should 'not show product price when showing product if not informed' do
  269 + product = fast_create(Product, :name => 'test product', :enterprise_id => @enterprise.id, :product_category_id => @product_category.id)
  270 + get :show, :id => product.id, :profile => @enterprise.identifier
  271 +
  272 + assert_no_tag :tag => 'span', :attributes => { :class => 'product_price' }, :content => /Price:/
  273 + end
  274 +
  275 + should 'show product price when showing product if unit was informed' do
  276 + prod = @enterprise.products.create!(:name => 'Product test', :price => 50.00, :unit => 'unit', :product_category => @product_category)
  277 + get :show, :id => prod.id, :profile => @enterprise.identifier
  278 +
  279 + assert_tag :tag => 'span', :attributes => { :class => 'field-name' }, :content => /Price:/
  280 + end
  281 +
  282 + should 'show product price when showing product if discount was informed' do
  283 + prod = @enterprise.products.create!(:name => 'Product test', :price => 50.00, :discount => 3.50, :unit => 'unit', :product_category => @product_category)
  284 + get :show, :id => prod.id, :profile => @enterprise.identifier
  285 +
  286 + assert_tag :tag => 'span', :attributes => { :class => 'field-name' }, :content => /List price:/
  287 + assert_tag :tag => 'span', :attributes => { :class => 'field-name' }, :content => /On sale:/
  288 + end
  289 +
  290 + should 'not show product price when showing product if unit not informed' do
  291 + prod = @enterprise.products.create!(:name => 'Product test', :price => 50.00, :product_category => @product_category)
  292 + get :show, :id => prod.id, :profile => @enterprise.identifier
  293 +
  294 + assert_no_tag :tag => 'span', :attributes => { :class => 'product_price' }, :content => /Price:/
  295 + end
  296 +
235 297 end
... ...
test/integration/routing_test.rb
... ... @@ -160,12 +160,6 @@ class RoutingTest &lt; ActionController::IntegrationTest
160 160  
161 161 def test_catalog_routing
162 162 assert_routing('/catalog/colivre', :controller => 'catalog', :action => 'index', :profile => 'colivre')
163   - assert_routing('/catalog/colivre/1234', :controller => 'catalog', :action => 'show', :profile => 'colivre', :id => '1234')
164   - end
165   -
166   - def test_catalog_with_dot_routing
167   - assert_routing('/catalog/profile.withdot', :controller => 'catalog', :action => 'index', :profile => 'profile.withdot')
168   - assert_routing('/catalog/profile.withdot/1234', :controller => 'catalog', :action => 'show', :profile => 'profile.withdot', :id => '1234')
169 163 end
170 164  
171 165 def test_hosted_domain_routing
... ...
test/unit/application_helper_test.rb
... ... @@ -579,11 +579,6 @@ class ApplicationHelperTest &lt; Test::Unit::TestCase
579 579 assert_equal 'filename.mp3', short_filename('filename.mp3')
580 580 end
581 581  
582   - include ActionView::Helpers::NumberHelper
583   - should 'format float to money as Brazillian currency' do
584   - assert_equal 'R$10,00', float_to_currency(10.0)
585   - end
586   -
587 582 protected
588 583  
589 584 def url_for(args = {})
... ...
test/unit/certifier_test.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class CertifierTest < Test::Unit::TestCase
  4 +
  5 + should 'have link' do
  6 + certifier = Certifier.new
  7 +
  8 + assert_equal '', certifier.link
  9 +
  10 + certifier.link = 'http://noosfero.org'
  11 + assert_equal 'http://noosfero.org', certifier.link
  12 + end
  13 +
  14 +end
... ...
test/unit/enterprise_homepage_test.rb
... ... @@ -52,7 +52,7 @@ class EnterpriseHomepageTest &lt; Test::Unit::TestCase
52 52 a = EnterpriseHomepage.new(:name => 'article homepage')
53 53 ent.articles << a
54 54 result = a.to_html
55   - assert_match /catalog\/test_enterprise\/#{prod.id}/, result
  55 + assert_match /\/test_enterprise\/manage_products\/show\/#{prod.id}/, result
56 56 end
57 57  
58 58 should 'can display hits' do
... ...
test/unit/environment_test.rb
... ... @@ -948,4 +948,28 @@ class EnvironmentTest &lt; Test::Unit::TestCase
948 948 assert !v.has_terms_of_use?
949 949 end
950 950  
  951 + should 'have currency unit attribute' do
  952 + env = Environment.new
  953 + assert_equal env.currency_unit, '$'
  954 +
  955 + env.currency_unit = 'R$'
  956 + assert_equal 'R$', env.currency_unit
  957 + end
  958 +
  959 + should 'have currency separator attribute' do
  960 + env = Environment.new
  961 + assert_equal env.currency_separator, '.'
  962 +
  963 + env.currency_separator = ','
  964 + assert_equal ',', env.currency_separator
  965 + end
  966 +
  967 + should 'have currency delimiter attribute' do
  968 + env = Environment.new
  969 + assert_equal env.currency_delimiter, ','
  970 +
  971 + env.currency_delimiter = '.'
  972 + assert_equal '.', env.currency_delimiter
  973 + end
  974 +
951 975 end
... ...
test/unit/manage_products_helper_test.rb
... ... @@ -38,6 +38,63 @@ class ManageProductsHelperTest &lt; Test::Unit::TestCase
38 38 assert_tag_in_string select_for_categories(category_1.children(true), 1), :tag => 'select', :attributes => {:id => 'category_id'}
39 39 end
40 40  
  41 + include ActionView::Helpers::NumberHelper
  42 + should 'format price to environment currency' do
  43 + @environment.currency_unit = "R$"
  44 + @environment.currency_separator = ","
  45 + @environment.currency_delimiter = "."
  46 + @environment.save
  47 + assert_equal 'R$ 10,00', float_to_currency(10.0)
  48 + end
  49 +
  50 + should 'not display link to edit product when user does not have permission' do
  51 + user = mock
  52 + user.expects(:has_permission?).with(anything, anything).returns(false)
  53 + @controller = mock
  54 + @controller.expects(:user).returns(user).at_least_once
  55 + @controller.expects(:profile).returns(mock)
  56 + category = fast_create(ProductCategory, :name => 'Category 1', :environment_id => @environment.id)
  57 + product = fast_create(Product, :product_category_id => category.id)
  58 + assert_equal '', edit_product_link(product, 'field', 'link to edit')
  59 + end
  60 +
  61 + should 'display link to edit product when user has permission' do
  62 + user = mock
  63 + user.expects(:has_permission?).with(anything, anything).returns(true)
  64 + @controller = mock
  65 + @controller.expects(:user).returns(user).at_least_once
  66 + @controller.expects(:profile).returns(mock)
  67 + category = fast_create(ProductCategory, :name => 'Category 1', :environment_id => @environment.id)
  68 + product = fast_create(Product, :product_category_id => category.id)
  69 +
  70 + expects(:link_to_remote).with('link to edit', {:update => "product-name", :url => {:controller => 'manage_products', :action => 'edit', :id => product.id, :field => 'name'}, :method => :get}, anything).returns('LINK')
  71 +
  72 + assert_equal 'LINK', edit_product_link(product, 'name', 'link to edit')
  73 + end
  74 +
  75 + should 'not display link to edit product category when user does not have permission' do
  76 + user = mock
  77 + user.expects(:has_permission?).with(anything, anything).returns(false)
  78 + @controller = mock
  79 + @controller.expects(:user).returns(user).at_least_once
  80 + @controller.expects(:profile).returns(mock)
  81 + category = fast_create(ProductCategory, :name => 'Category 1', :environment_id => @environment.id)
  82 + product = fast_create(Product, :product_category_id => category.id)
  83 + assert_equal '', edit_product_category_link(product)
  84 + end
  85 +
  86 + should 'display link to edit product category when user has permission' do
  87 + user = mock
  88 + user.expects(:has_permission?).with(anything, anything).returns(true)
  89 + @controller = mock
  90 + @controller.expects(:user).returns(user).at_least_once
  91 + @controller.expects(:profile).returns(mock)
  92 + category = fast_create(ProductCategory, :name => 'Category 1', :environment_id => @environment.id)
  93 + product = fast_create(Product, :product_category_id => category.id)
  94 +
  95 + assert_tag_in_string edit_product_category_link(product), {:tag => 'a', :content => 'Change category'}
  96 + end
  97 +
41 98 protected
42 99 include NoosferoTestHelper
43 100  
... ...
test/unit/product_test.rb
... ... @@ -20,6 +20,27 @@ class ProductTest &lt; Test::Unit::TestCase
20 20 end
21 21 end
22 22  
  23 + should 'display category name if name is nil' do
  24 + p = fast_create(Product, :name => nil)
  25 + p.expects(:category_name).returns('Software')
  26 + assert_equal 'Software', p.name
  27 + end
  28 +
  29 + should 'display category name if name is blank' do
  30 + p = fast_create(Product, :name => '')
  31 + p.expects(:category_name).returns('Software')
  32 + assert_equal 'Software', p.name
  33 + end
  34 +
  35 + should 'set nil to name if name is equal to category name' do
  36 + p = fast_create(Product)
  37 + p.expects(:category_name).returns('Software').at_least_once
  38 + p.name = 'Software'
  39 + p.save
  40 + assert_equal 'Software', p.name
  41 + assert_equal nil, p[:name]
  42 + end
  43 +
23 44 should 'list recent products' do
24 45 enterprise = fast_create(Enterprise, :name => "My enterprise", :identifier => 'my-enterprise')
25 46 Product.delete_all
... ... @@ -113,7 +134,7 @@ class ProductTest &lt; Test::Unit::TestCase
113 134  
114 135 product.expects(:id).returns(999)
115 136 product.expects(:enterprise).returns(enterprise)
116   - assert_equal({:controller => 'catalog', :action => 'show', :id => 999}, product.url)
  137 + assert_equal({:controller => 'manage_products', :action => 'show', :id => 999}, product.url)
117 138 end
118 139  
119 140 should 'categorize also with product categorization' do
... ... @@ -178,12 +199,27 @@ class ProductTest &lt; Test::Unit::TestCase
178 199 end
179 200 end
180 201  
  202 + should 'accept discount in american\'s or brazilian\'s currency format' do
  203 + [
  204 + [12.34, 12.34],
  205 + ["12.34", 12.34],
  206 + ["12,34", 12.34],
  207 + ["12.345.678,90", 12345678.90],
  208 + ["12,345,678.90", 12345678.90],
  209 + ["12.345.678", 12345678.00],
  210 + ["12,345,678", 12345678.00]
  211 + ].each do |input, output|
  212 + product = Product.new(:discount => input)
  213 + assert_equal output, product.discount
  214 + end
  215 + end
  216 +
181 217 should 'strip name with malformed HTML when sanitize' do
182 218 product = Product.new(:product_category => @product_category)
183 219 product.name = "<h1 Bla </h1>"
184 220 product.valid?
185 221  
186   - assert_equal '', product.name
  222 + assert_equal @product_category.name, product.name
187 223 end
188 224  
189 225 should 'escape malformed html tags' do
... ... @@ -208,4 +244,23 @@ class ProductTest &lt; Test::Unit::TestCase
208 244 assert product.errors.invalid?(:product_category)
209 245 end
210 246  
  247 + should 'format values to float with 2 decimals' do
  248 + ent = fast_create(Enterprise, :name => 'test ent 1', :identifier => 'test_ent1')
  249 + product = fast_create(Product, :enterprise_id => ent.id, :price => 12.994, :discount => 1.994)
  250 +
  251 + assert_equal "12.99", product.formatted_value(:price)
  252 + assert_equal "1.99", product.formatted_value(:discount)
  253 + end
  254 +
  255 + should 'calculate price with discount' do
  256 + ent = fast_create(Enterprise, :name => 'test ent 1', :identifier => 'test_ent1')
  257 + product = fast_create(Product, :enterprise_id => ent.id, :price => 12.994, :discount => 1.994)
  258 +
  259 + assert_equal 11.00, product.price_with_discount
  260 + end
  261 +
  262 + should 'have default image' do
  263 + product = Product.new
  264 + assert_equal '/images/icons-app/product-default-pic-thumb.png', product.default_image
  265 + end
211 266 end
... ...