From 192d4993a11c6ace3fdff25d53810160fcda2d3e Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 15 Nov 2012 13:42:55 +1100 Subject: [PATCH 01/29] Add EnterpriseFee model --- app/models/enterprise_fee.rb | 8 + .../20121115010717_create_enterprise_fees.rb | 11 ++ db/schema.rb | 158 +++++++++--------- spec/factories.rb | 3 + spec/models/enterprise_fee_spec.rb | 11 ++ 5 files changed, 116 insertions(+), 75 deletions(-) create mode 100644 app/models/enterprise_fee.rb create mode 100644 db/migrate/20121115010717_create_enterprise_fees.rb create mode 100644 spec/models/enterprise_fee_spec.rb diff --git a/app/models/enterprise_fee.rb b/app/models/enterprise_fee.rb new file mode 100644 index 0000000000..04223ea586 --- /dev/null +++ b/app/models/enterprise_fee.rb @@ -0,0 +1,8 @@ +class EnterpriseFee < ActiveRecord::Base + belongs_to :enterprise + + FEE_TYPES = %w(Packing Transport Admin Sales) + + validates_inclusion_of :fee_type, :in => FEE_TYPES + validates_presence_of :name +end diff --git a/db/migrate/20121115010717_create_enterprise_fees.rb b/db/migrate/20121115010717_create_enterprise_fees.rb new file mode 100644 index 0000000000..00c3057bfb --- /dev/null +++ b/db/migrate/20121115010717_create_enterprise_fees.rb @@ -0,0 +1,11 @@ +class CreateEnterpriseFees < ActiveRecord::Migration + def change + create_table :enterprise_fees do |t| + t.references :enterprise + t.string :fee_type + t.string :name + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 4bbccb5c05..f0e2589174 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20121031222403) do +ActiveRecord::Schema.define(:version => 20121115010717) do create_table "cms_blocks", :force => true do |t| t.integer "page_id", :null => false @@ -130,6 +130,14 @@ ActiveRecord::Schema.define(:version => 20121031222403) do add_index "cms_snippets", ["site_id", "identifier"], :name => "index_cms_snippets_on_site_id_and_identifier", :unique => true add_index "cms_snippets", ["site_id", "position"], :name => "index_cms_snippets_on_site_id_and_position" + create_table "enterprise_fees", :force => true do |t| + t.integer "enterprise_id" + t.string "fee_type" + t.string "name" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "enterprises", :force => true do |t| t.string "name" t.string "description" @@ -186,8 +194,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.string "alternative_phone" t.integer "state_id" t.integer "country_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "company" end @@ -196,12 +204,12 @@ ActiveRecord::Schema.define(:version => 20121031222403) do create_table "spree_adjustments", :force => true do |t| t.integer "source_id" - t.decimal "amount", :precision => 8, :scale => 2, :default => 0.0 + t.decimal "amount", :precision => 8, :scale => 2 t.string "label" t.string "source_type" t.integer "adjustable_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.boolean "mandatory" t.boolean "locked" t.integer "originator_id" @@ -233,15 +241,15 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.string "type" t.integer "calculable_id", :null => false t.string "calculable_type", :null => false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_configurations", :force => true do |t| t.string "name" t.string "type", :limit => 50 - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end add_index "spree_configurations", ["name", "type"], :name => "index_configurations_on_name_and_type" @@ -279,8 +287,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.string "environment", :default => "development" t.string "server", :default => "test" t.boolean "test_mode", :default => true - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_inventory_units", :force => true do |t| @@ -288,8 +296,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.string "state" t.integer "variant_id" t.integer "order_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "shipment_id" t.integer "return_authorization_id" end @@ -303,8 +311,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.integer "variant_id" t.integer "quantity", :null => false t.decimal "price", :precision => 8, :scale => 2, :null => false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "max_quantity" t.integer "shipping_method_id" end @@ -316,22 +324,22 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.integer "source_id" t.string "source_type" t.text "details" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_mail_methods", :force => true do |t| t.string "environment" t.boolean "active", :default => true - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_option_types", :force => true do |t| t.string "name", :limit => 100 t.string "presentation", :limit => 100 - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "position", :default => 0, :null => false end @@ -345,8 +353,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.string "name" t.string "presentation" t.integer "option_type_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_option_values_variants", :id => false, :force => true do |t| @@ -365,8 +373,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.decimal "adjustment_total", :precision => 8, :scale => 2, :default => 0.0, :null => false t.decimal "credit_total", :precision => 8, :scale => 2, :default => 0.0, :null => false t.integer "user_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.datetime "completed_at" t.integer "bill_address_id" t.integer "ship_address_id" @@ -387,8 +395,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.text "description" t.boolean "active", :default => true t.string "environment", :default => "development" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.datetime "deleted_at" t.string "display_on" end @@ -396,8 +404,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do create_table "spree_payments", :force => true do |t| t.decimal "amount", :precision => 8, :scale => 2, :default => 0.0, :null => false t.integer "order_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "source_id" t.string "source_type" t.integer "payment_method_id" @@ -426,8 +434,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.integer "owner_id" t.string "owner_type" t.text "value" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "key" t.string "value_type" end @@ -452,16 +460,16 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.integer "position" t.integer "product_id" t.integer "option_type_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_product_properties", :force => true do |t| t.string "value" t.integer "product_id" t.integer "property_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end add_index "spree_product_properties", ["product_id"], :name => "index_product_properties_on_product_id" @@ -485,8 +493,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.string "meta_keywords" t.integer "tax_category_id" t.integer "shipping_category_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "count_on_hand", :default => 0, :null => false t.integer "supplier_id" t.boolean "group_buy" @@ -531,8 +539,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.integer "user_id" t.integer "product_group_id" t.string "type" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end add_index "spree_promotion_rules", ["product_group_id"], :name => "index_promotion_rules_on_product_group_id" @@ -549,8 +557,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do create_table "spree_properties", :force => true do |t| t.string "name" t.string "presentation", :null => false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_properties_prototypes", :id => false, :force => true do |t| @@ -560,8 +568,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do create_table "spree_prototypes", :force => true do |t| t.string "name" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_return_authorizations", :force => true do |t| @@ -570,8 +578,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.decimal "amount", :precision => 8, :scale => 2, :default => 0.0, :null => false t.integer "order_id" t.text "reason" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_roles", :force => true do |t| @@ -594,8 +602,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.integer "order_id" t.integer "shipping_method_id" t.integer "address_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "state" end @@ -603,15 +611,15 @@ ActiveRecord::Schema.define(:version => 20121031222403) do create_table "spree_shipping_categories", :force => true do |t| t.string "name" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_shipping_methods", :force => true do |t| t.string "name" t.integer "zone_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "display_on" t.integer "shipping_category_id" t.boolean "match_none" @@ -627,8 +635,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.integer "transaction_id" t.integer "customer_id" t.string "payment_type" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_state_changes", :force => true do |t| @@ -636,8 +644,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.string "previous_state" t.integer "stateful_id" t.integer "user_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "stateful_type" t.string "next_state" end @@ -651,8 +659,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do create_table "spree_tax_categories", :force => true do |t| t.string "name" t.string "description" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.boolean "is_default", :default => false t.datetime "deleted_at" end @@ -661,15 +669,15 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.decimal "amount", :precision => 8, :scale => 5 t.integer "zone_id" t.integer "tax_category_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.boolean "included_in_price", :default => false end create_table "spree_taxonomies", :force => true do |t| t.string "name", :null => false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_taxons", :force => true do |t| @@ -678,8 +686,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.string "name", :null => false t.string "permalink" t.integer "taxonomy_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "lft" t.integer "rgt" t.string "icon_file_name" @@ -697,8 +705,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.integer "permissable_id" t.string "permissable_type" t.string "token" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end add_index "spree_tokenized_permissions", ["permissable_id", "permissable_type"], :name => "index_tokenized_name_and_type" @@ -707,8 +715,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.string "environment" t.string "analytics_id" t.boolean "active", :default => true - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_users", :force => true do |t| @@ -729,8 +737,8 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.string "login" t.integer "ship_address_id" t.integer "bill_address_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "authentication_token" t.string "unlock_token" t.datetime "locked_at" @@ -753,7 +761,7 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.boolean "is_master", :default => false t.integer "product_id" t.integer "count_on_hand", :default => 0, :null => false - t.decimal "cost_price", :precision => 8, :scale => 2, :default => 0.0 + t.decimal "cost_price", :precision => 8, :scale => 2 t.integer "position" end @@ -763,15 +771,15 @@ ActiveRecord::Schema.define(:version => 20121031222403) do t.integer "zoneable_id" t.string "zoneable_type" t.integer "zone_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "spree_zones", :force => true do |t| t.string "name" t.string "description" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.boolean "default_tax", :default => false t.integer "zone_members_count", :default => 0 end diff --git a/spec/factories.rb b/spec/factories.rb index 8348f98afd..d4b6504514 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -20,6 +20,9 @@ FactoryGirl.define do is_distributor true end + factory :enterprise_fee, :class => EnterpriseFee do + end + factory :product_distribution, :class => ProductDistribution do product { |pd| Spree::Product.first || FactoryGirl.create(:product) } distributor { |pd| Enterprise.is_distributor.first || FactoryGirl.create(:distributor_enterprise) } diff --git a/spec/models/enterprise_fee_spec.rb b/spec/models/enterprise_fee_spec.rb new file mode 100644 index 0000000000..1616b0decd --- /dev/null +++ b/spec/models/enterprise_fee_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe EnterpriseFee do + describe "associations" do + it { should belong_to(:enterprise) } + end + + describe "validations" do + it { should validate_presence_of(:name) } + end +end From ec743966593f8468a83b98a373e75dd24ca91825 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 15 Nov 2012 13:58:51 +1100 Subject: [PATCH 02/29] Add enterprise fees to admin configurations menu --- .../add_enterprise_fees_to_admin_configurations_menu.rb | 4 ++++ .../enterprise_fees/_admin_configurations_menu.html.haml | 3 +++ config/routes.rb | 1 + 3 files changed, 8 insertions(+) create mode 100644 app/overrides/add_enterprise_fees_to_admin_configurations_menu.rb create mode 100644 app/views/enterprise_fees/_admin_configurations_menu.html.haml diff --git a/app/overrides/add_enterprise_fees_to_admin_configurations_menu.rb b/app/overrides/add_enterprise_fees_to_admin_configurations_menu.rb new file mode 100644 index 0000000000..a363758646 --- /dev/null +++ b/app/overrides/add_enterprise_fees_to_admin_configurations_menu.rb @@ -0,0 +1,4 @@ +Deface::Override.new(:virtual_path => "spree/admin/configurations/index", + :name => "add_enterprise_fees_to_admin_configurations_menu", + :insert_bottom => "[data-hook='admin_configurations_menu']", + :partial => 'enterprise_fees/admin_configurations_menu') diff --git a/app/views/enterprise_fees/_admin_configurations_menu.html.haml b/app/views/enterprise_fees/_admin_configurations_menu.html.haml new file mode 100644 index 0000000000..13045a0fb2 --- /dev/null +++ b/app/views/enterprise_fees/_admin_configurations_menu.html.haml @@ -0,0 +1,3 @@ +%tr + %td= link_to "Enterprise Fees", main_app.admin_enterprise_fees_path + %td Create and manage fees charged by enterprises on order cycles diff --git a/config/routes.rb b/config/routes.rb index 60454a3ae1..31871a0ecc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,6 +12,7 @@ Openfoodweb::Application.routes.draw do resources :enterprises do post :bulk_update, :on => :collection, :as => :bulk_update end + resources :enterprise_fees end # Mount Spree's routes From 03764881c8e4d3bd62aa7952df482c90fe149188 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 15 Nov 2012 14:29:38 +1100 Subject: [PATCH 03/29] Generalise EnterpriseSet to ModelSet --- .../admin/enterprises_controller.rb | 4 +-- app/models/enterprise_set.rb | 34 ------------------- app/models/model_set.rb | 34 +++++++++++++++++++ app/views/admin/enterprises/index.html.erb | 2 +- spec/requests/admin/enterprises_spec.rb | 8 ++--- 5 files changed, 41 insertions(+), 41 deletions(-) delete mode 100644 app/models/enterprise_set.rb create mode 100644 app/models/model_set.rb diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index 08f6e2133f..d0ad4bfac6 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -6,7 +6,7 @@ module Admin helper 'spree/products' def bulk_update - @enterprise_set = EnterpriseSet.new(params[:enterprise_set]) + @enterprise_set = ModelSet.new(Enterprise.all, params[:model_set]) if @enterprise_set.save redirect_to main_app.admin_enterprises_path, :notice => 'Distributor collection times updated.' else @@ -16,7 +16,7 @@ module Admin private def load_enterprise_set - @enterprise_set = EnterpriseSet.new :enterprises => collection + @enterprise_set = ModelSet.new Enterprise.all, :collection => collection end def load_countries diff --git a/app/models/enterprise_set.rb b/app/models/enterprise_set.rb deleted file mode 100644 index b741167dfe..0000000000 --- a/app/models/enterprise_set.rb +++ /dev/null @@ -1,34 +0,0 @@ -# Tableless model to handle updating multiple enterprises at once from a -# single form. Used to update next_collection_at field for all distributors in -# admin backend. -class EnterpriseSet - include ActiveModel::Conversion - extend ActiveModel::Naming - - attr_accessor :enterprises - - def initialize(attributes={}) - @enterprises = Enterprise.all - - attributes.each do |name, value| - send("#{name}=", value) - end - end - - def enterprises_attributes=(attributes) - attributes.each do |k, attributes| - # attributes == {:id => 123, :next_collection_at => '...'} - e = @enterprises.detect { |e| e.id.to_s == attributes[:id].to_s } - e.assign_attributes(attributes.except(:id)) - end - end - - def save - enterprises.all?(&:save) - end - - def persisted? - false - end - -end diff --git a/app/models/model_set.rb b/app/models/model_set.rb new file mode 100644 index 0000000000..e806e44014 --- /dev/null +++ b/app/models/model_set.rb @@ -0,0 +1,34 @@ +# Tableless model to handle updating multiple models at once from a +# single form. For example, it is used to update the enterprise next_collection_at +# field for all distributors in the admin backend. +class ModelSet + include ActiveModel::Conversion + extend ActiveModel::Naming + + attr_accessor :collection + + def initialize(collection, attributes={}) + @collection = collection + + attributes.each do |name, value| + send("#{name}=", value) + end + end + + def collection_attributes=(attributes) + attributes.each do |k, attributes| + # attributes == {:id => 123, :next_collection_at => '...'} + e = @collection.detect { |e| e.id.to_s == attributes[:id].to_s } + e.assign_attributes(attributes.except(:id)) + end + end + + def save + collection.all?(&:save) + end + + def persisted? + false + end + +end diff --git a/app/views/admin/enterprises/index.html.erb b/app/views/admin/enterprises/index.html.erb index 75dad0f85f..c1e0869b86 100644 --- a/app/views/admin/enterprises/index.html.erb +++ b/app/views/admin/enterprises/index.html.erb @@ -19,7 +19,7 @@ - <%= f.fields_for :enterprises do |enterprise_form| %> + <%= f.fields_for :collection do |enterprise_form| %> <% enterprise = enterprise_form.object %> <%= link_to enterprise.name, main_app.admin_enterprise_path(enterprise) %> diff --git a/spec/requests/admin/enterprises_spec.rb b/spec/requests/admin/enterprises_spec.rb index dae560df69..535970cf55 100644 --- a/spec/requests/admin/enterprises_spec.rb +++ b/spec/requests/admin/enterprises_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" feature %q{ As an administrator - I want manage enterprises + I want to manage enterprises } do include AuthenticationWorkflow include WebHelper @@ -109,9 +109,9 @@ feature %q{ click_link 'Enterprises' # And I fill in some new collection times and save them - fill_in 'enterprise_set_enterprises_attributes_0_next_collection_at', :with => 'One' - fill_in 'enterprise_set_enterprises_attributes_1_next_collection_at', :with => 'Two' - fill_in 'enterprise_set_enterprises_attributes_2_next_collection_at', :with => 'Three' + fill_in 'model_set_collection_attributes_0_next_collection_at', :with => 'One' + fill_in 'model_set_collection_attributes_1_next_collection_at', :with => 'Two' + fill_in 'model_set_collection_attributes_2_next_collection_at', :with => 'Three' click_button 'Update' # Then my times should have been saved From 6247bd2541bb10f1edf4f6d4f503cce3f7ae3d75 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 15 Nov 2012 15:03:12 +1100 Subject: [PATCH 04/29] Display admin table of enterprise fees with enterprise, fee type and name columns --- .../admin/enterprise_fees_controller.rb | 10 ++++++++++ app/helpers/enterprise_fees_helper.rb | 5 +++++ app/models/enterprise_fee.rb | 2 +- .../admin/enterprise_fees/index.html.haml | 20 +++++++++++++++++++ config/routes.rb | 4 +++- spec/factories.rb | 3 +++ 6 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 app/controllers/admin/enterprise_fees_controller.rb create mode 100644 app/helpers/enterprise_fees_helper.rb create mode 100644 app/views/admin/enterprise_fees/index.html.haml diff --git a/app/controllers/admin/enterprise_fees_controller.rb b/app/controllers/admin/enterprise_fees_controller.rb new file mode 100644 index 0000000000..bdb74d59dd --- /dev/null +++ b/app/controllers/admin/enterprise_fees_controller.rb @@ -0,0 +1,10 @@ +module Admin + class EnterpriseFeesController < ResourceController + before_filter :load_enterprise_fees_set, :only => :index + + private + def load_enterprise_fees_set + @enterprise_fees_set = ModelSet.new EnterpriseFee.all, :collection => collection + end + end +end diff --git a/app/helpers/enterprise_fees_helper.rb b/app/helpers/enterprise_fees_helper.rb new file mode 100644 index 0000000000..2399a1737e --- /dev/null +++ b/app/helpers/enterprise_fees_helper.rb @@ -0,0 +1,5 @@ +module EnterpriseFeesHelper + def enterprise_fee_options + EnterpriseFee::FEE_TYPES.map { |f| [f.capitalize, f] } + end +end diff --git a/app/models/enterprise_fee.rb b/app/models/enterprise_fee.rb index 04223ea586..ea5ba91446 100644 --- a/app/models/enterprise_fee.rb +++ b/app/models/enterprise_fee.rb @@ -1,7 +1,7 @@ class EnterpriseFee < ActiveRecord::Base belongs_to :enterprise - FEE_TYPES = %w(Packing Transport Admin Sales) + FEE_TYPES = %w(packing transport admin sales) validates_inclusion_of :fee_type, :in => FEE_TYPES validates_presence_of :name diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml new file mode 100644 index 0000000000..bdac2f5ce5 --- /dev/null +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -0,0 +1,20 @@ += form_for @enterprise_fees_set, :url => main_app.bulk_update_admin_enterprise_fees_path do |enterprise_fees_set_form| + %table.index#listing_enterprise_fees + %thead + %tr + %th Enterprise + %th Fee Type + %th Name + %th Calculator + %th Calculator values + %th + %tbody + = enterprise_fees_set_form.fields_for :collection do |f| + - enterprise_fee = f.object + %tr + %td= f.collection_select :enterprise_id, Enterprise.all, :id, :name + %td= f.select :fee_type, enterprise_fee_options + %td= f.text_field :name + %td TODO + %td TODO + %td TODO diff --git a/config/routes.rb b/config/routes.rb index 31871a0ecc..0ad57b0304 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,7 +12,9 @@ Openfoodweb::Application.routes.draw do resources :enterprises do post :bulk_update, :on => :collection, :as => :bulk_update end - resources :enterprise_fees + resources :enterprise_fees do + post :bulk_update, :on => :collection, :as => :bulk_update + end end # Mount Spree's routes diff --git a/spec/factories.rb b/spec/factories.rb index d4b6504514..6b76ab47c7 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -21,6 +21,9 @@ FactoryGirl.define do end factory :enterprise_fee, :class => EnterpriseFee do + enterprise { Enterprise.first || FactoryGirl.create(:supplier_enterprise) } + fee_type 'packing' + name '$0.50 / kg' end factory :product_distribution, :class => ProductDistribution do From 98143ffe5b19c3758a1c8cfbc5f450bf792acfe3 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 16 Nov 2012 16:07:40 +1100 Subject: [PATCH 05/29] Add calculator to EnterpriseFee, test passes for listing enterprise fees --- .../admin/enterprise_fees_controller.rb | 6 +++++ app/models/enterprise_fee.rb | 3 +++ .../admin/enterprise_fees/index.html.haml | 7 ++++-- config/application.rb | 7 ++++++ config/initializers/spree.rb | 14 +++++++++++ spec/factories.rb | 9 ++++++++ spec/requests/admin/enterprise_fees_spec.rb | 23 +++++++++++++++++++ 7 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 spec/requests/admin/enterprise_fees_spec.rb diff --git a/app/controllers/admin/enterprise_fees_controller.rb b/app/controllers/admin/enterprise_fees_controller.rb index bdb74d59dd..2799aa68be 100644 --- a/app/controllers/admin/enterprise_fees_controller.rb +++ b/app/controllers/admin/enterprise_fees_controller.rb @@ -1,10 +1,16 @@ module Admin class EnterpriseFeesController < ResourceController before_filter :load_enterprise_fees_set, :only => :index + before_filter :load_data private def load_enterprise_fees_set @enterprise_fees_set = ModelSet.new EnterpriseFee.all, :collection => collection end + + def load_data + @calculators = EnterpriseFee.calculators.sort_by(&:name) + end + end end diff --git a/app/models/enterprise_fee.rb b/app/models/enterprise_fee.rb index ea5ba91446..25b2622e78 100644 --- a/app/models/enterprise_fee.rb +++ b/app/models/enterprise_fee.rb @@ -1,6 +1,9 @@ class EnterpriseFee < ActiveRecord::Base belongs_to :enterprise + calculated_adjustments + has_one :calculator, :as => :calculable, :dependent => :destroy, :class_name => 'Spree::Calculator' + FEE_TYPES = %w(packing transport admin sales) validates_inclusion_of :fee_type, :in => FEE_TYPES diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index bdac2f5ce5..23b2ddddb5 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -15,6 +15,9 @@ %td= f.collection_select :enterprise_id, Enterprise.all, :id, :name %td= f.select :fee_type, enterprise_fee_options %td= f.text_field :name - %td TODO - %td TODO + %td= f.collection_select :calculator_type, @calculators, :name, :description + %td + - if !enterprise_fee.new_record? + = f.fields_for :calculator do |calculator_form| + = preference_fields(enterprise_fee.calculator, calculator_form) %td TODO diff --git a/config/application.rb b/config/application.rb index 0e77035c39..24d5009745 100644 --- a/config/application.rb +++ b/config/application.rb @@ -28,6 +28,13 @@ module Openfoodweb initializer "spree.register.calculators" do |app| app.config.spree.calculators.shipping_methods << OpenFoodWeb::Calculator::Itemwise app.config.spree.calculators.shipping_methods << OpenFoodWeb::Calculator::Weight + + app.config.spree.calculators.enterprise_fees = [Spree::Calculator::FlatPercentItemTotal, + Spree::Calculator::FlatRate, + Spree::Calculator::FlexiRate, + Spree::Calculator::PerItem, + Spree::Calculator::PriceSack, + OpenFoodWeb::Calculator::Weight] end diff --git a/config/initializers/spree.rb b/config/initializers/spree.rb index 0a5d763b52..59982c1a9e 100644 --- a/config/initializers/spree.rb +++ b/config/initializers/spree.rb @@ -26,3 +26,17 @@ Spree.config do |config| # Auto-capture payments. Without this option, payments must be manually captured in the paypal interface. config.auto_capture = true end + + +# Add calculators category for enterprise fees +module Spree + module Core + class Environment + class Calculators + include EnvironmentExtension + + attr_accessor :enterprise_fees + end + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 6b76ab47c7..b643b92f1d 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -24,6 +24,9 @@ FactoryGirl.define do enterprise { Enterprise.first || FactoryGirl.create(:supplier_enterprise) } fee_type 'packing' name '$0.50 / kg' + calculator { FactoryGirl.build(:weight_calculator) } + + after_create { |ef| ef.calculator.save! } end factory :product_distribution, :class => ProductDistribution do @@ -39,6 +42,12 @@ FactoryGirl.define do factory :itemwise_calculator, :class => OpenFoodWeb::Calculator::Itemwise do end + + factory :weight_calculator, :class => OpenFoodWeb::Calculator::Weight do + after_build { |c| c.set_preference(:per_kg, 0.5) } + after_create { |c| c.set_preference(:per_kg, 0.5); c.save! } + end + end diff --git a/spec/requests/admin/enterprise_fees_spec.rb b/spec/requests/admin/enterprise_fees_spec.rb new file mode 100644 index 0000000000..08fc134ceb --- /dev/null +++ b/spec/requests/admin/enterprise_fees_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +feature %q{ + As an administrator + I want to manage enterprise fees +} do + include AuthenticationWorkflow + include WebHelper + + scenario "listing enterprise fees" do + fee = create(:enterprise_fee) + + login_to_admin_section + click_link 'Configuration' + click_link 'Enterprise Fees' + + page.should have_selector 'option', :text => fee.enterprise.name + page.should have_selector 'option', :text => 'Packing' + page.should have_selector "input[value='$0.50 / kg']" + page.should have_selector 'option', :text => 'Weight (per kg)' + page.should have_selector "input[value='0.5']" + end +end From ff0d2bc9835317590cdfe0276b5fdd898b74de42 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 21 Nov 2012 09:08:41 +1100 Subject: [PATCH 06/29] Create EnterpriseSet as subclass of ModelSet --- app/controllers/admin/enterprises_controller.rb | 4 ++-- app/models/enterprise_set.rb | 5 +++++ app/models/model_set.rb | 4 +--- spec/requests/admin/enterprises_spec.rb | 6 +++--- 4 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 app/models/enterprise_set.rb diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index d0ad4bfac6..a297eadc9c 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -6,7 +6,7 @@ module Admin helper 'spree/products' def bulk_update - @enterprise_set = ModelSet.new(Enterprise.all, params[:model_set]) + @enterprise_set = EnterpriseSet.new(params[:enterprise_set]) if @enterprise_set.save redirect_to main_app.admin_enterprises_path, :notice => 'Distributor collection times updated.' else @@ -16,7 +16,7 @@ module Admin private def load_enterprise_set - @enterprise_set = ModelSet.new Enterprise.all, :collection => collection + @enterprise_set = EnterpriseSet.new :collection => collection end def load_countries diff --git a/app/models/enterprise_set.rb b/app/models/enterprise_set.rb new file mode 100644 index 0000000000..f267810c62 --- /dev/null +++ b/app/models/enterprise_set.rb @@ -0,0 +1,5 @@ +class EnterpriseSet < ModelSet + def initialize(attributes={}) + super(Enterprise.all, attributes) + end +end diff --git a/app/models/model_set.rb b/app/models/model_set.rb index e806e44014..737fb9d625 100644 --- a/app/models/model_set.rb +++ b/app/models/model_set.rb @@ -1,6 +1,4 @@ -# Tableless model to handle updating multiple models at once from a -# single form. For example, it is used to update the enterprise next_collection_at -# field for all distributors in the admin backend. +# Tableless model to handle updating multiple models at once from a single form class ModelSet include ActiveModel::Conversion extend ActiveModel::Naming diff --git a/spec/requests/admin/enterprises_spec.rb b/spec/requests/admin/enterprises_spec.rb index 535970cf55..5c4b433852 100644 --- a/spec/requests/admin/enterprises_spec.rb +++ b/spec/requests/admin/enterprises_spec.rb @@ -109,9 +109,9 @@ feature %q{ click_link 'Enterprises' # And I fill in some new collection times and save them - fill_in 'model_set_collection_attributes_0_next_collection_at', :with => 'One' - fill_in 'model_set_collection_attributes_1_next_collection_at', :with => 'Two' - fill_in 'model_set_collection_attributes_2_next_collection_at', :with => 'Three' + fill_in 'enterprise_set_collection_attributes_0_next_collection_at', :with => 'One' + fill_in 'enterprise_set_collection_attributes_1_next_collection_at', :with => 'Two' + fill_in 'enterprise_set_collection_attributes_2_next_collection_at', :with => 'Three' click_button 'Update' # Then my times should have been saved From a4d10ab4e08956ecbf1734f04199148c66639522 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 21 Nov 2012 09:47:51 +1100 Subject: [PATCH 07/29] Create new enterprise fee, display errors when present --- .../admin/enterprise_fees_controller.rb | 20 +++++++++-- app/models/enterprise_fee.rb | 2 ++ app/models/enterprise_fee_set.rb | 7 ++++ app/models/enterprise_set.rb | 2 +- app/models/model_set.rb | 17 ++++++--- .../admin/enterprise_fees/index.html.haml | 12 +++++-- spec/requests/admin/enterprise_fees_spec.rb | 35 +++++++++++++++++-- 7 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 app/models/enterprise_fee_set.rb diff --git a/app/controllers/admin/enterprise_fees_controller.rb b/app/controllers/admin/enterprise_fees_controller.rb index 2799aa68be..9b6966c40e 100644 --- a/app/controllers/admin/enterprise_fees_controller.rb +++ b/app/controllers/admin/enterprise_fees_controller.rb @@ -1,16 +1,30 @@ module Admin class EnterpriseFeesController < ResourceController - before_filter :load_enterprise_fees_set, :only => :index + before_filter :load_enterprise_fee_set, :only => :index before_filter :load_data + def bulk_update + @enterprise_fee_set = EnterpriseFeeSet.new(params[:enterprise_fee_set]) + if @enterprise_fee_set.save + redirect_to main_app.admin_enterprise_fees_path, :notice => 'Your enterprise fees have been updated.' + else + render :index + end + end + + private - def load_enterprise_fees_set - @enterprise_fees_set = ModelSet.new EnterpriseFee.all, :collection => collection + def load_enterprise_fee_set + @enterprise_fee_set = EnterpriseFeeSet.new :collection => collection end def load_data @calculators = EnterpriseFee.calculators.sort_by(&:name) end + def collection + super + (1..3).map { EnterpriseFee.new } + end + end end diff --git a/app/models/enterprise_fee.rb b/app/models/enterprise_fee.rb index 25b2622e78..8107f1fdf7 100644 --- a/app/models/enterprise_fee.rb +++ b/app/models/enterprise_fee.rb @@ -4,6 +4,8 @@ class EnterpriseFee < ActiveRecord::Base calculated_adjustments has_one :calculator, :as => :calculable, :dependent => :destroy, :class_name => 'Spree::Calculator' + attr_accessible :enterprise_id, :fee_type, :name, :calculator_type + FEE_TYPES = %w(packing transport admin sales) validates_inclusion_of :fee_type, :in => FEE_TYPES diff --git a/app/models/enterprise_fee_set.rb b/app/models/enterprise_fee_set.rb new file mode 100644 index 0000000000..4ae02f51d8 --- /dev/null +++ b/app/models/enterprise_fee_set.rb @@ -0,0 +1,7 @@ +class EnterpriseFeeSet < ModelSet + def initialize(attributes={}) + super(EnterpriseFee, EnterpriseFee.all, + proc { |attrs| attrs[:enterprise_id].blank? }, + attributes) + end +end diff --git a/app/models/enterprise_set.rb b/app/models/enterprise_set.rb index f267810c62..81591b6dad 100644 --- a/app/models/enterprise_set.rb +++ b/app/models/enterprise_set.rb @@ -1,5 +1,5 @@ class EnterpriseSet < ModelSet def initialize(attributes={}) - super(Enterprise.all, attributes) + super(Enterprise, Enterprise.all, nil, attributes) end end diff --git a/app/models/model_set.rb b/app/models/model_set.rb index 737fb9d625..778a148b82 100644 --- a/app/models/model_set.rb +++ b/app/models/model_set.rb @@ -5,8 +5,9 @@ class ModelSet attr_accessor :collection - def initialize(collection, attributes={}) - @collection = collection + + def initialize(klass, collection, reject_if, attributes={}) + @klass, @collection, @reject_if = klass, collection, reject_if attributes.each do |name, value| send("#{name}=", value) @@ -16,11 +17,19 @@ class ModelSet def collection_attributes=(attributes) attributes.each do |k, attributes| # attributes == {:id => 123, :next_collection_at => '...'} - e = @collection.detect { |e| e.id.to_s == attributes[:id].to_s } - e.assign_attributes(attributes.except(:id)) + e = @collection.detect { |e| e.id.to_s == attributes[:id].to_s && !e.id.nil? } + if e.nil? + @collection << @klass.new(attributes) unless @reject_if.andand.call(attributes) + else + e.assign_attributes(attributes.except(:id)) + end end end + def errors + @collection.map { |ef| ef.errors.full_messages }.flatten + end + def save collection.all?(&:save) end diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index 23b2ddddb5..4f2fb2ae85 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -1,4 +1,9 @@ -= form_for @enterprise_fees_set, :url => main_app.bulk_update_admin_enterprise_fees_path do |enterprise_fees_set_form| += form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path do |enterprise_fee_set_form| + - if @enterprise_fee_set.errors.present? + %h2 Errors + %ul + - @enterprise_fee_set.errors.each do |error| + %li= error %table.index#listing_enterprise_fees %thead %tr @@ -9,10 +14,10 @@ %th Calculator values %th %tbody - = enterprise_fees_set_form.fields_for :collection do |f| + = enterprise_fee_set_form.fields_for :collection do |f| - enterprise_fee = f.object %tr - %td= f.collection_select :enterprise_id, Enterprise.all, :id, :name + %td= f.collection_select :enterprise_id, Enterprise.all, :id, :name, :include_blank => true %td= f.select :fee_type, enterprise_fee_options %td= f.text_field :name %td= f.collection_select :calculator_type, @calculators, :name, :description @@ -21,3 +26,4 @@ = f.fields_for :calculator do |calculator_form| = preference_fields(enterprise_fee.calculator, calculator_form) %td TODO + = enterprise_fee_set_form.submit 'Update' diff --git a/spec/requests/admin/enterprise_fees_spec.rb b/spec/requests/admin/enterprise_fees_spec.rb index 08fc134ceb..d4d583860b 100644 --- a/spec/requests/admin/enterprise_fees_spec.rb +++ b/spec/requests/admin/enterprise_fees_spec.rb @@ -14,10 +14,39 @@ feature %q{ click_link 'Configuration' click_link 'Enterprise Fees' - page.should have_selector 'option', :text => fee.enterprise.name - page.should have_selector 'option', :text => 'Packing' + page.should have_selector 'option', text: fee.enterprise.name + page.should have_selector 'option', text: 'Packing' page.should have_selector "input[value='$0.50 / kg']" - page.should have_selector 'option', :text => 'Weight (per kg)' + page.should have_selector 'option', text: 'Weight (per kg)' page.should have_selector "input[value='0.5']" end + + scenario "creating an enterprise fee" do + # Given an enterprise + e = create(:supplier_enterprise, name: 'Feedme') + + # When I go to the enterprise fees page + login_to_admin_section + click_link 'Configuration' + click_link 'Enterprise Fees' + + # And I fill in the fields for a new enterprise fee and click update + select 'Feedme', from: 'enterprise_fee_set_collection_attributes_0_enterprise_id' + select 'Admin', from: 'enterprise_fee_set_collection_attributes_0_fee_type' + fill_in 'enterprise_fee_set_collection_attributes_0_name', with: 'Hello!' + select 'Flat Percent', from: 'enterprise_fee_set_collection_attributes_0_calculator_type' + click_button 'Update' + + # Then I should see my fee and fields for the calculator + page.should have_content "Your enterprise fees have been updated." + page.should have_selector "input[value='Hello!']" + + # When I fill in the calculator fields and click update + fill_in 'enterprise_fee_set_collection_attributes_0_calculator_attributes_preferred_flat_percent', with: '12.34' + click_button 'Update' + + # Then I should see the correct values in my calculator fields + page.should have_selector "#enterprise_fee_set_collection_attributes_0_calculator_attributes_preferred_flat_percent[value='12.34']" + end + end From bcc2ef99fd58998cacbdf51da67a19d3bd150529 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 21 Nov 2012 11:49:59 +1100 Subject: [PATCH 08/29] Ensure that AUTH_TOKEN var is set in tests, since spree's admin JS errors without it --- app/overrides/set_auth_token_in_test.rb | 9 +++++++++ app/views/layouts/_auth_token_script.html.haml | 4 ++++ 2 files changed, 13 insertions(+) create mode 100644 app/overrides/set_auth_token_in_test.rb create mode 100644 app/views/layouts/_auth_token_script.html.haml diff --git a/app/overrides/set_auth_token_in_test.rb b/app/overrides/set_auth_token_in_test.rb new file mode 100644 index 0000000000..eb1b7128b3 --- /dev/null +++ b/app/overrides/set_auth_token_in_test.rb @@ -0,0 +1,9 @@ +Deface::Override.new(:virtual_path => "spree/layouts/spree_application", + :insert_bottom => "[data-hook='inside_head']", + :partial => "layouts/auth_token_script", + :name => "auth_token_script") + +Deface::Override.new(:virtual_path => "spree/layouts/admin", + :insert_bottom => "[data-hook='admin_inside_head']", + :partial => "layouts/auth_token_script", + :name => "auth_token_script") diff --git a/app/views/layouts/_auth_token_script.html.haml b/app/views/layouts/_auth_token_script.html.haml new file mode 100644 index 0000000000..bc0f6c8dc4 --- /dev/null +++ b/app/views/layouts/_auth_token_script.html.haml @@ -0,0 +1,4 @@ +:javascript + if(typeof AUTH_TOKEN === 'undefined') { + var AUTH_TOKEN = ''; + } From 77b78f5bbb0f91e9b551fa069c8207ed8f2206b6 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 21 Nov 2012 11:50:37 +1100 Subject: [PATCH 09/29] Edit enterprise fee --- .../javascripts/admin/enterprise_fees.js | 26 +++++++++++++++ .../admin/enterprise_fees/index.html.haml | 7 ++-- spec/requests/admin/enterprise_fees_spec.rb | 33 +++++++++++++++++-- 3 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 app/assets/javascripts/admin/enterprise_fees.js diff --git a/app/assets/javascripts/admin/enterprise_fees.js b/app/assets/javascripts/admin/enterprise_fees.js new file mode 100644 index 0000000000..e07b0d86ce --- /dev/null +++ b/app/assets/javascripts/admin/enterprise_fees.js @@ -0,0 +1,26 @@ +// Hide calculator preference fields when calculator type changed +// Fixes 'Enterprise fee is not found' error when changing calculator type +// See spree/core/app/assets/javascripts/admin/calculator.js + +$(document).ready(function() { + // Store original value + $("select.calculator_type").each(function(i, ct) { + ct = $(ct); + ct.data('original-value', ct.attr('value')); + }); + + // Hide and disable calculator fields when calculator type is changed + $("select.calculator_type").change(function() { + var ct = $(this); + var cs = ct.parent().parent().find("div.calculator-settings"); + + if(ct.attr('value') == ct.data('original-value')) { + cs.show(); + cs.find("input").prop("disabled", false); + + } else { + cs.hide(); + cs.find("input").prop("disabled", true); + } + }); +}); diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index 4f2fb2ae85..213923160f 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -20,10 +20,11 @@ %td= f.collection_select :enterprise_id, Enterprise.all, :id, :name, :include_blank => true %td= f.select :fee_type, enterprise_fee_options %td= f.text_field :name - %td= f.collection_select :calculator_type, @calculators, :name, :description + %td= f.collection_select :calculator_type, @calculators, :name, :description, {}, {:class => 'calculator_type'} %td - if !enterprise_fee.new_record? - = f.fields_for :calculator do |calculator_form| - = preference_fields(enterprise_fee.calculator, calculator_form) + .calculator-settings + = f.fields_for :calculator do |calculator_form| + = preference_fields(enterprise_fee.calculator, calculator_form) %td TODO = enterprise_fee_set_form.submit 'Update' diff --git a/spec/requests/admin/enterprise_fees_spec.rb b/spec/requests/admin/enterprise_fees_spec.rb index d4d583860b..da4fdb6547 100644 --- a/spec/requests/admin/enterprise_fees_spec.rb +++ b/spec/requests/admin/enterprise_fees_spec.rb @@ -14,10 +14,10 @@ feature %q{ click_link 'Configuration' click_link 'Enterprise Fees' - page.should have_selector 'option', text: fee.enterprise.name - page.should have_selector 'option', text: 'Packing' + page.should have_selector "option[selected]", text: fee.enterprise.name + page.should have_selector "option[selected]", text: 'Packing' page.should have_selector "input[value='$0.50 / kg']" - page.should have_selector 'option', text: 'Weight (per kg)' + page.should have_selector "option[selected]", text: 'Weight (per kg)' page.should have_selector "input[value='0.5']" end @@ -49,4 +49,31 @@ feature %q{ page.should have_selector "#enterprise_fee_set_collection_attributes_0_calculator_attributes_preferred_flat_percent[value='12.34']" end + scenario "editing an enterprise fee", js: true do + # Given an enterprise fee + fee = create(:enterprise_fee) + create(:enterprise, name: 'Foo') + + # When I go to the enterprise fees page + login_to_admin_section + click_link 'Configuration' + click_link 'Enterprise Fees' + binding.pry + + # And I update the fields for the enterprise fee and click update + select 'Foo', from: 'enterprise_fee_set_collection_attributes_0_enterprise_id' + select 'Admin', from: 'enterprise_fee_set_collection_attributes_0_fee_type' + fill_in 'enterprise_fee_set_collection_attributes_0_name', with: 'Greetings!' + select 'Flat Percent', from: 'enterprise_fee_set_collection_attributes_0_calculator_type' + binding.pry + click_button 'Update' + + # Then I should see the updated fields for my fee + page.should have_selector "option[selected]", text: 'Foo' + page.should have_selector "option[selected]", text: 'Admin' + page.should have_selector "input[value='Greetings!']" + page.should have_selector "option[selected]", text: 'Flat Percent' + end + + end From fa2addd0255c5ae0df6b0dbb4e443ca9a61f3327 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 21 Nov 2012 12:17:19 +1100 Subject: [PATCH 10/29] Delete enterprise fee --- .../admin/enterprise_fees/index.html.haml | 2 +- spec/requests/admin/enterprise_fees_spec.rb | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index 213923160f..79bd95d755 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -26,5 +26,5 @@ .calculator-settings = f.fields_for :calculator do |calculator_form| = preference_fields(enterprise_fee.calculator, calculator_form) - %td TODO + %td= link_to_delete enterprise_fee unless enterprise_fee.new_record? = enterprise_fee_set_form.submit 'Update' diff --git a/spec/requests/admin/enterprise_fees_spec.rb b/spec/requests/admin/enterprise_fees_spec.rb index da4fdb6547..90f7f065d3 100644 --- a/spec/requests/admin/enterprise_fees_spec.rb +++ b/spec/requests/admin/enterprise_fees_spec.rb @@ -58,14 +58,12 @@ feature %q{ login_to_admin_section click_link 'Configuration' click_link 'Enterprise Fees' - binding.pry # And I update the fields for the enterprise fee and click update select 'Foo', from: 'enterprise_fee_set_collection_attributes_0_enterprise_id' select 'Admin', from: 'enterprise_fee_set_collection_attributes_0_fee_type' fill_in 'enterprise_fee_set_collection_attributes_0_name', with: 'Greetings!' select 'Flat Percent', from: 'enterprise_fee_set_collection_attributes_0_calculator_type' - binding.pry click_button 'Update' # Then I should see the updated fields for my fee @@ -75,5 +73,22 @@ feature %q{ page.should have_selector "option[selected]", text: 'Flat Percent' end + scenario "deleting an enterprise fee", js: true do + # Given an enterprise fee + fee = create(:enterprise_fee) + + # When I go to the enterprise fees page + login_to_admin_section + click_link 'Configuration' + click_link 'Enterprise Fees' + + # And I click delete + click_link 'Delete' + page.driver.wait_until(page.driver.browser.switch_to.alert.accept) + + # Then my enterprise fee should have been deleted + visit admin_enterprise_fees_path + page.should_not have_selector "input[value='#{fee.name}']" + end end From 63ba5d13a303ba91a1e1ac41649fa4ad7125416b Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 21 Nov 2012 14:58:29 +1100 Subject: [PATCH 11/29] Add representative, output enterprise fees as JSON --- Gemfile | 1 + Gemfile.lock | 9 +++++++++ app/controllers/admin/enterprise_fees_controller.rb | 7 +++++++ app/views/admin/enterprise_fees/index.rep | 6 ++++++ 4 files changed, 23 insertions(+) create mode 100644 app/views/admin/enterprise_fees/index.rep diff --git a/Gemfile b/Gemfile index 4041b11ff3..475085b938 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,7 @@ gem 'haml' gem 'aws-sdk' gem 'andand' gem 'truncate_html' +gem 'representative_view' # Gems used only for assets and not required # in production environments by default. diff --git a/Gemfile.lock b/Gemfile.lock index 2e5e808e92..fdd805ffb3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -282,6 +282,14 @@ GEM polyamorous (~> 0.5.0) rdoc (3.12) json (~> 1.4) + representative (1.0.5) + activesupport (>= 2.2.2) + builder (>= 2.1.2) + i18n (>= 0.4.1) + nokogiri (>= 1.4.2) + representative_view (1.2.2) + actionpack (> 2.3.0, < 4.0.0) + representative (~> 1.0.2) rspec (2.10.0) rspec-core (~> 2.10.0) rspec-expectations (~> 2.10.0) @@ -359,6 +367,7 @@ DEPENDENCIES pg pry-debugger rails (= 3.2.8) + representative_view rspec-rails sass-rails (~> 3.2.3) shoulda-matchers diff --git a/app/controllers/admin/enterprise_fees_controller.rb b/app/controllers/admin/enterprise_fees_controller.rb index 9b6966c40e..690fd69a05 100644 --- a/app/controllers/admin/enterprise_fees_controller.rb +++ b/app/controllers/admin/enterprise_fees_controller.rb @@ -3,6 +3,13 @@ module Admin before_filter :load_enterprise_fee_set, :only => :index before_filter :load_data + def index + respond_to do |format| + format.html + format.json + end + end + def bulk_update @enterprise_fee_set = EnterpriseFeeSet.new(params[:enterprise_fee_set]) if @enterprise_fee_set.save diff --git a/app/views/admin/enterprise_fees/index.rep b/app/views/admin/enterprise_fees/index.rep new file mode 100644 index 0000000000..d1b7bb46dc --- /dev/null +++ b/app/views/admin/enterprise_fees/index.rep @@ -0,0 +1,6 @@ +r.list_of :enterprise_fees, @collection do + r.element :id + r.element :enterprise_id + r.element :fee_type + r.element :name +end From dd155532f8bc77bc9c0396dca729ad7bd96af31f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 21 Nov 2012 14:59:02 +1100 Subject: [PATCH 12/29] Add angular include, basic controller for listing enterprise fees, display fee ids using angular --- app/assets/javascripts/admin/enterprise_fees.js | 16 ++++++++++++++++ app/views/admin/enterprise_fees/index.html.haml | 7 ++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/enterprise_fees.js b/app/assets/javascripts/admin/enterprise_fees.js index e07b0d86ce..e2cbb28bbc 100644 --- a/app/assets/javascripts/admin/enterprise_fees.js +++ b/app/assets/javascripts/admin/enterprise_fees.js @@ -1,3 +1,18 @@ +function AdminEnterpriseFeesCtrl($scope, $http) { + $http.get('/admin/enterprise_fees.json').success(function(data) { + $scope.enterprise_fees = data; + }); +} + + + + + + + + + +/* // Hide calculator preference fields when calculator type changed // Fixes 'Enterprise fee is not found' error when changing calculator type // See spree/core/app/assets/javascripts/admin/calculator.js @@ -24,3 +39,4 @@ $(document).ready(function() { } }); }); +*/ \ No newline at end of file diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index 79bd95d755..4e7bdd6187 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -1,10 +1,13 @@ +- content_for :head do + = javascript_include_tag "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js" + = form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path do |enterprise_fee_set_form| - if @enterprise_fee_set.errors.present? %h2 Errors %ul - @enterprise_fee_set.errors.each do |error| %li= error - %table.index#listing_enterprise_fees + %table.index#listing_enterprise_fees{'ng-app' => '', 'ng-controller' => 'AdminEnterpriseFeesCtrl'} %thead %tr %th Enterprise @@ -14,6 +17,8 @@ %th Calculator values %th %tbody + %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} + %td {{ enterprise_fee.id }} = enterprise_fee_set_form.fields_for :collection do |f| - enterprise_fee = f.object %tr From 58917fe378a37446cfa559c8271e2108c774e51b Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 22 Nov 2012 08:58:08 +1100 Subject: [PATCH 13/29] Convert enterprise select and name entry into form fields --- app/views/admin/enterprise_fees/index.html.haml | 11 ++++++++++- app/views/admin/enterprise_fees/index.rep | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index 4e7bdd6187..484be94643 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -18,7 +18,16 @@ %th %tbody %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} - %td {{ enterprise_fee.id }} + %td + %select{:id => 'enterprise_fee_set_collection_attributes_{{ $index }}_enterprise_id', :name => "enterprise_fee_set[collection_attributes][{{ $index }}][enterprise_id]", 'ng-model' => 'enterprise_fee'} + %option{:value => ""} + - Enterprise.all.each do |enterprise| + %option{:value => enterprise.id, 'ng-selected' => "enterprise_fee.enterprise_id == #{enterprise.id}"}= enterprise.name + %td {{ enterprise_fee.fee_type }} + %td= text_field_tag 'enterprise_fee_set[collection_attributes][{{ $index }}][name]', '{{ enterprise_fee.name }}', :id => 'enterprise_fee_set_collection_attributes_{{ $index }}_name' + %td {{ enterprise_fee.calculator_type }} + + = enterprise_fee_set_form.fields_for :collection do |f| - enterprise_fee = f.object %tr diff --git a/app/views/admin/enterprise_fees/index.rep b/app/views/admin/enterprise_fees/index.rep index d1b7bb46dc..fe0db9eb97 100644 --- a/app/views/admin/enterprise_fees/index.rep +++ b/app/views/admin/enterprise_fees/index.rep @@ -3,4 +3,5 @@ r.list_of :enterprise_fees, @collection do r.element :enterprise_id r.element :fee_type r.element :name + r.element :calculator_type end From d6802d9ae5664050f3bffb8d8dab233629dad758 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 22 Nov 2012 09:53:26 +1100 Subject: [PATCH 14/29] Generate text field with custom form builder - AngularFormBuilder --- app/helpers/angular_form_builder.rb | 20 +++++++++++++++++++ app/helpers/application_helper.rb | 7 +++++++ .../admin/enterprise_fees/index.html.haml | 10 +++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 app/helpers/angular_form_builder.rb diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb new file mode 100644 index 0000000000..8d33c938f8 --- /dev/null +++ b/app/helpers/angular_form_builder.rb @@ -0,0 +1,20 @@ +class AngularFormBuilder < ActionView::Helpers::FormBuilder + def angular_fields_for(record_name, *args, &block) + # TODO: Handle nested angular_fields_for + @fields_for_record_name = record_name + block.call self + @fields_for_record_name = nil + end + + def angular_text_field(method, options = {}) + # @object_name --> "enterprise_fee_set" + # @fields_for_record_name --> :collection + # @object.send(@fields_for_record_name).first.class.to_s.underscore --> enterprise_fee + + name = "#{@object_name}[#{@fields_for_record_name}_attributes][{{ $index }}][#{method}]" + id = "#{@object_name}_#{@fields_for_record_name}_attributes_{{ $index }}_#{method}" + + @template.text_field_tag name, :id => id + end + +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 4f41a1a0ed..5035bf8809 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -6,6 +6,13 @@ module ApplicationHelper end + def angular_form_for(name, *args, &block) + options = args.extract_options! + + form_for(name, *(args << options.merge(:builder => AngularFormBuilder)), &block) + end + + # Pass URL helper calls on to spree where applicable so that we don't need to use # spree.foo_path in any view rendered from non-spree-namespaced controllers. def method_missing(method, *args, &block) diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index 484be94643..b278bf6399 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -1,7 +1,7 @@ - content_for :head do = javascript_include_tag "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js" -= form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path do |enterprise_fee_set_form| += angular_form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path do |enterprise_fee_set_form| - if @enterprise_fee_set.errors.present? %h2 Errors %ul @@ -17,6 +17,14 @@ %th Calculator values %th %tbody + = enterprise_fee_set_form.angular_fields_for :collection do |f| + %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} + %td=# f.collection_select :enterprise_id, Enterprise.all, :id, :name, :include_blank => true + %td=# f.select :fee_type, enterprise_fee_options + %td= f.angular_text_field :name + %td=# f.collection_select :calculator_type, @calculators, :name, :description, {}, {:class => 'calculator_type'} + + %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} %td %select{:id => 'enterprise_fee_set_collection_attributes_{{ $index }}_enterprise_id', :name => "enterprise_fee_set[collection_attributes][{{ $index }}][enterprise_id]", 'ng-model' => 'enterprise_fee'} From 216125c7a16c4f258f576d0a6e7c309dbaa771df Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 22 Nov 2012 11:03:28 +1100 Subject: [PATCH 15/29] Start building some selects --- app/helpers/angular_form_builder.rb | 26 ++++++++++++++++++- .../admin/enterprise_fees/index.html.haml | 12 +++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb index 8d33c938f8..007462dccc 100644 --- a/app/helpers/angular_form_builder.rb +++ b/app/helpers/angular_form_builder.rb @@ -1,4 +1,6 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder + # TODO: Use ng_ prefix, like ng_fields_for + def angular_fields_for(record_name, *args, &block) # TODO: Handle nested angular_fields_for @fields_for_record_name = record_name @@ -13,8 +15,30 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder name = "#{@object_name}[#{@fields_for_record_name}_attributes][{{ $index }}][#{method}]" id = "#{@object_name}_#{@fields_for_record_name}_attributes_{{ $index }}_#{method}" + value = "{{ #{@object.send(@fields_for_record_name).first.class.to_s.underscore}.#{method} }}" - @template.text_field_tag name, :id => id + @template.text_field_tag name, value, :id => id end + def angular_select(method, choices, options = {}, html_options = {}) + # ... + end + + def angular_options_for_select(container, selected = nil) + return container if String === container + + selected, disabled = extract_selected_and_disabled(selected).map do | r | + Array.wrap(r).map { |item| item.to_s } + end + + container.map do |element| + html_attributes = option_html_attributes(element) + text, value = option_text_and_value(element).map { |item| item.to_s } + selected_attribute = %Q( ng-selected="#{selected}") if selected + disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled) + %() + end.join("\n").html_safe + + + end end diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index b278bf6399..fa6a3d4af4 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -17,25 +17,27 @@ %th Calculator values %th %tbody - = enterprise_fee_set_form.angular_fields_for :collection do |f| + -# -- Finished product + = enterprise_fee_set_form.angular_fields_for :collection do |f| # regular fields_for collection.first ?? %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} %td=# f.collection_select :enterprise_id, Enterprise.all, :id, :name, :include_blank => true - %td=# f.select :fee_type, enterprise_fee_options + %td=# f.angular_select :fee_type, enterprise_fee_options %td= f.angular_text_field :name %td=# f.collection_select :calculator_type, @calculators, :name, :description, {}, {:class => 'calculator_type'} - + -# -- Manual HTML / form_tag %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} %td %select{:id => 'enterprise_fee_set_collection_attributes_{{ $index }}_enterprise_id', :name => "enterprise_fee_set[collection_attributes][{{ $index }}][enterprise_id]", 'ng-model' => 'enterprise_fee'} %option{:value => ""} - Enterprise.all.each do |enterprise| %option{:value => enterprise.id, 'ng-selected' => "enterprise_fee.enterprise_id == #{enterprise.id}"}= enterprise.name - %td {{ enterprise_fee.fee_type }} + %td + {{ enterprise_fee.fee_type }} %td= text_field_tag 'enterprise_fee_set[collection_attributes][{{ $index }}][name]', '{{ enterprise_fee.name }}', :id => 'enterprise_fee_set_collection_attributes_{{ $index }}_name' %td {{ enterprise_fee.calculator_type }} - + -# -- Plain old Rails = enterprise_fee_set_form.fields_for :collection do |f| - enterprise_fee = f.object %tr From ad76e669446f0e601bdb498d83236ca03385bef4 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 22 Nov 2012 11:24:05 +1100 Subject: [PATCH 16/29] angular_options_for_select --- app/helpers/angular_form_builder.rb | 18 ------------------ app/helpers/angular_form_helper.rb | 16 ++++++++++++++++ app/helpers/enterprise_fees_helper.rb | 2 +- .../admin/enterprise_fees/index.html.haml | 13 ++++++------- 4 files changed, 23 insertions(+), 26 deletions(-) create mode 100644 app/helpers/angular_form_helper.rb diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb index 007462dccc..63785b0d31 100644 --- a/app/helpers/angular_form_builder.rb +++ b/app/helpers/angular_form_builder.rb @@ -23,22 +23,4 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder def angular_select(method, choices, options = {}, html_options = {}) # ... end - - def angular_options_for_select(container, selected = nil) - return container if String === container - - selected, disabled = extract_selected_and_disabled(selected).map do | r | - Array.wrap(r).map { |item| item.to_s } - end - - container.map do |element| - html_attributes = option_html_attributes(element) - text, value = option_text_and_value(element).map { |item| item.to_s } - selected_attribute = %Q( ng-selected="#{selected}") if selected - disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled) - %() - end.join("\n").html_safe - - - end end diff --git a/app/helpers/angular_form_helper.rb b/app/helpers/angular_form_helper.rb new file mode 100644 index 0000000000..02c086f8eb --- /dev/null +++ b/app/helpers/angular_form_helper.rb @@ -0,0 +1,16 @@ +module AngularFormHelper + def angular_options_for_select(container, angular_field=nil) + return container if String === container + + container.map do |element| + html_attributes = option_html_attributes(element) + text, value = option_text_and_value(element).map { |item| item.to_s } + selected_attribute = %Q( ng-selected="#{angular_field} == '#{value}'") if angular_field + %() + end.join("\n").html_safe + end +end + +class ActionView::Helpers::InstanceTag + include AngularFormHelper +end diff --git a/app/helpers/enterprise_fees_helper.rb b/app/helpers/enterprise_fees_helper.rb index 2399a1737e..b7ec2b9018 100644 --- a/app/helpers/enterprise_fees_helper.rb +++ b/app/helpers/enterprise_fees_helper.rb @@ -1,5 +1,5 @@ module EnterpriseFeesHelper - def enterprise_fee_options + def enterprise_fee_type_options EnterpriseFee::FEE_TYPES.map { |f| [f.capitalize, f] } end end diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index fa6a3d4af4..69175778f6 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -17,32 +17,31 @@ %th Calculator values %th %tbody - -# -- Finished product - = enterprise_fee_set_form.angular_fields_for :collection do |f| # regular fields_for collection.first ?? + / -- Finished product + = enterprise_fee_set_form.angular_fields_for :collection do |f| %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} %td=# f.collection_select :enterprise_id, Enterprise.all, :id, :name, :include_blank => true %td=# f.angular_select :fee_type, enterprise_fee_options %td= f.angular_text_field :name %td=# f.collection_select :calculator_type, @calculators, :name, :description, {}, {:class => 'calculator_type'} - -# -- Manual HTML / form_tag + / -- Manual HTML / form_tag %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} %td %select{:id => 'enterprise_fee_set_collection_attributes_{{ $index }}_enterprise_id', :name => "enterprise_fee_set[collection_attributes][{{ $index }}][enterprise_id]", 'ng-model' => 'enterprise_fee'} %option{:value => ""} - Enterprise.all.each do |enterprise| %option{:value => enterprise.id, 'ng-selected' => "enterprise_fee.enterprise_id == #{enterprise.id}"}= enterprise.name - %td - {{ enterprise_fee.fee_type }} + %td= select_tag :fee_type, angular_options_for_select(enterprise_fee_type_options, 'enterprise_fee.fee_type') %td= text_field_tag 'enterprise_fee_set[collection_attributes][{{ $index }}][name]', '{{ enterprise_fee.name }}', :id => 'enterprise_fee_set_collection_attributes_{{ $index }}_name' %td {{ enterprise_fee.calculator_type }} - -# -- Plain old Rails + / -- Plain old Rails = enterprise_fee_set_form.fields_for :collection do |f| - enterprise_fee = f.object %tr %td= f.collection_select :enterprise_id, Enterprise.all, :id, :name, :include_blank => true - %td= f.select :fee_type, enterprise_fee_options + %td= f.select :fee_type, enterprise_fee_type_options %td= f.text_field :name %td= f.collection_select :calculator_type, @calculators, :name, :description, {}, {:class => 'calculator_type'} %td From e64a86f5be9692369066d9f3d9524b25804f4fde Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 22 Nov 2012 11:27:51 +1100 Subject: [PATCH 17/29] angular_select --- app/helpers/angular_form_builder.rb | 4 ++-- app/views/admin/enterprise_fees/index.html.haml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb index 63785b0d31..1ace68b0f1 100644 --- a/app/helpers/angular_form_builder.rb +++ b/app/helpers/angular_form_builder.rb @@ -20,7 +20,7 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder @template.text_field_tag name, value, :id => id end - def angular_select(method, choices, options = {}, html_options = {}) - # ... + def angular_select(method, choices, angular_field, options = {}, html_options = {}) + @template.select_tag method, @template.angular_options_for_select(choices, angular_field) end end diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index 69175778f6..9817c039c5 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -21,7 +21,7 @@ = enterprise_fee_set_form.angular_fields_for :collection do |f| %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} %td=# f.collection_select :enterprise_id, Enterprise.all, :id, :name, :include_blank => true - %td=# f.angular_select :fee_type, enterprise_fee_options + %td= f.angular_select :fee_type, enterprise_fee_type_options, 'enterprise_fee.fee_type' %td= f.angular_text_field :name %td=# f.collection_select :calculator_type, @calculators, :name, :description, {}, {:class => 'calculator_type'} From 873937db47397f64d4e730a64b4e167c3e18b4fc Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 22 Nov 2012 11:44:32 +1100 Subject: [PATCH 18/29] angular_collection_select --- app/helpers/angular_form_builder.rb | 9 +++++++-- app/helpers/angular_form_helper.rb | 9 +++++++++ app/views/admin/enterprise_fees/index.html.haml | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb index 1ace68b0f1..ef783f9b17 100644 --- a/app/helpers/angular_form_builder.rb +++ b/app/helpers/angular_form_builder.rb @@ -20,7 +20,12 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder @template.text_field_tag name, value, :id => id end - def angular_select(method, choices, angular_field, options = {}, html_options = {}) - @template.select_tag method, @template.angular_options_for_select(choices, angular_field) + def angular_select(method, choices, angular_field, options = {}) + @template.select_tag method, @template.angular_options_for_select(choices, angular_field), options end + + def angular_collection_select(method, collection, value_method, text_method, angular_field, options = {}) + @template.select_tag method, @template.angular_options_from_collection_for_select(collection, value_method, text_method, angular_field), options + end + end diff --git a/app/helpers/angular_form_helper.rb b/app/helpers/angular_form_helper.rb index 02c086f8eb..9116d403fa 100644 --- a/app/helpers/angular_form_helper.rb +++ b/app/helpers/angular_form_helper.rb @@ -9,8 +9,17 @@ module AngularFormHelper %() end.join("\n").html_safe end + + def angular_options_from_collection_for_select(collection, value_method, text_method, angular_field) + options = collection.map do |element| + [element.send(text_method), element.send(value_method)] + end + + angular_options_for_select(options, angular_field) + end end + class ActionView::Helpers::InstanceTag include AngularFormHelper end diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index 9817c039c5..b5cfe12668 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -20,10 +20,10 @@ / -- Finished product = enterprise_fee_set_form.angular_fields_for :collection do |f| %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} - %td=# f.collection_select :enterprise_id, Enterprise.all, :id, :name, :include_blank => true + %td= f.angular_collection_select :enterprise_id, Enterprise.all, :id, :name, 'enterprise_fee.enterprise_id', :include_blank => true %td= f.angular_select :fee_type, enterprise_fee_type_options, 'enterprise_fee.fee_type' %td= f.angular_text_field :name - %td=# f.collection_select :calculator_type, @calculators, :name, :description, {}, {:class => 'calculator_type'} + %td= f.angular_collection_select :calculator_type, @calculators, :name, :description, 'enterprise_fee.calculator_type', {:class => 'calculator_type'} / -- Manual HTML / form_tag %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} From b7bd21e49883074793c241c56498b0489abd33c1 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 22 Nov 2012 12:15:39 +1100 Subject: [PATCH 19/29] Full-text search of form --- app/assets/stylesheets/admin/openfoodweb.css.scss | 6 ++++++ app/helpers/angular_form_builder.rb | 4 ++-- app/models/enterprise_fee.rb | 10 ++++++++++ app/views/admin/enterprise_fees/index.html.haml | 14 +++++++++++--- app/views/admin/enterprise_fees/index.rep | 2 ++ 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/admin/openfoodweb.css.scss b/app/assets/stylesheets/admin/openfoodweb.css.scss index 0f535b3f58..633b768371 100644 --- a/app/assets/stylesheets/admin/openfoodweb.css.scss +++ b/app/assets/stylesheets/admin/openfoodweb.css.scss @@ -12,3 +12,9 @@ float: none; margin-bottom: 0; } + + +#new_enterprise_fee_set input.search { + float: right; + margin-bottom: 1em; +} diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb index ef783f9b17..3d51291cbc 100644 --- a/app/helpers/angular_form_builder.rb +++ b/app/helpers/angular_form_builder.rb @@ -21,11 +21,11 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder end def angular_select(method, choices, angular_field, options = {}) - @template.select_tag method, @template.angular_options_for_select(choices, angular_field), options + @template.select_tag method, @template.angular_options_for_select(choices, angular_field), options.reverse_merge!({'ng-model' => angular_field}) end def angular_collection_select(method, collection, value_method, text_method, angular_field, options = {}) - @template.select_tag method, @template.angular_options_from_collection_for_select(collection, value_method, text_method, angular_field), options + @template.select_tag method, @template.angular_options_from_collection_for_select(collection, value_method, text_method, angular_field), options.reverse_merge!({'ng-model' => angular_field}) end end diff --git a/app/models/enterprise_fee.rb b/app/models/enterprise_fee.rb index 8107f1fdf7..6ed6683575 100644 --- a/app/models/enterprise_fee.rb +++ b/app/models/enterprise_fee.rb @@ -10,4 +10,14 @@ class EnterpriseFee < ActiveRecord::Base validates_inclusion_of :fee_type, :in => FEE_TYPES validates_presence_of :name + + + def enterprise_name + enterprise.andand.name + end + + def calculator_description + calculator.andand.description + end + end diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index b5cfe12668..42a3a27ec8 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -1,13 +1,16 @@ - content_for :head do = javascript_include_tag "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js" -= angular_form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path do |enterprise_fee_set_form| += angular_form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path, :html => {'ng-app' => '', 'ng-controller' => 'AdminEnterpriseFeesCtrl'} do |enterprise_fee_set_form| - if @enterprise_fee_set.errors.present? %h2 Errors %ul - @enterprise_fee_set.errors.each do |error| %li= error - %table.index#listing_enterprise_fees{'ng-app' => '', 'ng-controller' => 'AdminEnterpriseFeesCtrl'} + + %input.search{'ng-model' => 'query', 'placeholder' => 'Search'} + + %table.index#listing_enterprise_fees %thead %tr %th Enterprise @@ -19,11 +22,16 @@ %tbody / -- Finished product = enterprise_fee_set_form.angular_fields_for :collection do |f| - %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} + %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees | filter:query'} %td= f.angular_collection_select :enterprise_id, Enterprise.all, :id, :name, 'enterprise_fee.enterprise_id', :include_blank => true %td= f.angular_select :fee_type, enterprise_fee_type_options, 'enterprise_fee.fee_type' %td= f.angular_text_field :name %td= f.angular_collection_select :calculator_type, @calculators, :name, :description, 'enterprise_fee.calculator_type', {:class => 'calculator_type'} + %td {{ enterprise_fee.calculator_settings }} + %td Delete + + %tr + %td --- / -- Manual HTML / form_tag %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} diff --git a/app/views/admin/enterprise_fees/index.rep b/app/views/admin/enterprise_fees/index.rep index fe0db9eb97..f2ba072dee 100644 --- a/app/views/admin/enterprise_fees/index.rep +++ b/app/views/admin/enterprise_fees/index.rep @@ -1,7 +1,9 @@ r.list_of :enterprise_fees, @collection do r.element :id r.element :enterprise_id + r.element :enterprise_name r.element :fee_type r.element :name r.element :calculator_type + r.element :calculator_description end From 2a645d04ae5f51a57940b0605733ec1c144f6e9d Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 22 Nov 2012 14:37:38 +1100 Subject: [PATCH 20/29] Move presentation to presenter, display calculator fields --- .../admin/enterprise_fees_controller.rb | 2 +- app/controllers/application_controller.rb | 12 +++++++ app/models/enterprise_fee.rb | 10 ------ app/presenters/enterprise_fee_presenter.rb | 32 +++++++++++++++++++ .../_calculator_settings.html.haml | 9 ++++++ .../admin/enterprise_fees/index.html.haml | 2 +- app/views/admin/enterprise_fees/index.rep | 3 +- config/application.rb | 2 +- 8 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 app/presenters/enterprise_fee_presenter.rb create mode 100644 app/views/admin/enterprise_fees/_calculator_settings.html.haml diff --git a/app/controllers/admin/enterprise_fees_controller.rb b/app/controllers/admin/enterprise_fees_controller.rb index 690fd69a05..2958ab6c73 100644 --- a/app/controllers/admin/enterprise_fees_controller.rb +++ b/app/controllers/admin/enterprise_fees_controller.rb @@ -6,7 +6,7 @@ module Admin def index respond_to do |format| format.html - format.json + format.json { @presented_collection = @collection.map { |ef| EnterpriseFeePresenter.new(self, ef) } } end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b5bcef894e..b3ef2dff28 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -14,4 +14,16 @@ class ApplicationController < ActionController::Base @distributors = Enterprise.is_distributor.with_distributed_active_products_on_hand.by_name end + # All render calls within the block will be performed with the specified format + # Useful for rendering html within a JSON response, particularly if the specified + # template or partial then goes on to render further partials without specifying + # their format. + def with_format(format, &block) + old_formats = formats + self.formats = [format] + block.call + self.formats = old_formats + nil + end + end diff --git a/app/models/enterprise_fee.rb b/app/models/enterprise_fee.rb index 6ed6683575..8107f1fdf7 100644 --- a/app/models/enterprise_fee.rb +++ b/app/models/enterprise_fee.rb @@ -10,14 +10,4 @@ class EnterpriseFee < ActiveRecord::Base validates_inclusion_of :fee_type, :in => FEE_TYPES validates_presence_of :name - - - def enterprise_name - enterprise.andand.name - end - - def calculator_description - calculator.andand.description - end - end diff --git a/app/presenters/enterprise_fee_presenter.rb b/app/presenters/enterprise_fee_presenter.rb new file mode 100644 index 0000000000..bbecfc0f4f --- /dev/null +++ b/app/presenters/enterprise_fee_presenter.rb @@ -0,0 +1,32 @@ +class EnterpriseFeePresenter + def initialize(controller, enterprise_fee) + @controller = controller + @enterprise_fee = enterprise_fee + end + + delegate :id, :enterprise_id, :fee_type, :name, :calculator_type, :to => :enterprise_fee + + def enterprise_fee + @enterprise_fee + end + + + def enterprise_name + @enterprise_fee.enterprise.andand.name + end + + def calculator_description + @enterprise_fee.calculator.andand.description + end + + def calculator_settings + result = nil + + @controller.send(:with_format, :html) do + result = @controller.render_to_string :partial => 'admin/enterprise_fees/calculator_settings', :locals => {:enterprise_fee => @enterprise_fee} + end + + result + end + +end diff --git a/app/views/admin/enterprise_fees/_calculator_settings.html.haml b/app/views/admin/enterprise_fees/_calculator_settings.html.haml new file mode 100644 index 0000000000..8a81602b71 --- /dev/null +++ b/app/views/admin/enterprise_fees/_calculator_settings.html.haml @@ -0,0 +1,9 @@ +-# Render only the calculator settings and not the surrounding form +- form = nil +- form_for enterprise_fee, :url => '' do |f| + - form = capture do + - if !enterprise_fee.new_record? + .calculator-settings + = f.fields_for :calculator do |calculator_form| + = preference_fields(enterprise_fee.calculator, calculator_form) += form diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index 42a3a27ec8..c5af08e0a3 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -27,7 +27,7 @@ %td= f.angular_select :fee_type, enterprise_fee_type_options, 'enterprise_fee.fee_type' %td= f.angular_text_field :name %td= f.angular_collection_select :calculator_type, @calculators, :name, :description, 'enterprise_fee.calculator_type', {:class => 'calculator_type'} - %td {{ enterprise_fee.calculator_settings }} + %td{'ng-bind-html-unsafe' => 'enterprise_fee.calculator_settings'} %td Delete %tr diff --git a/app/views/admin/enterprise_fees/index.rep b/app/views/admin/enterprise_fees/index.rep index f2ba072dee..c6bccc5a4e 100644 --- a/app/views/admin/enterprise_fees/index.rep +++ b/app/views/admin/enterprise_fees/index.rep @@ -1,4 +1,4 @@ -r.list_of :enterprise_fees, @collection do +r.list_of :enterprise_fees, @presented_collection do r.element :id r.element :enterprise_id r.element :enterprise_name @@ -6,4 +6,5 @@ r.list_of :enterprise_fees, @collection do r.element :name r.element :calculator_type r.element :calculator_description + r.element :calculator_settings end diff --git a/config/application.rb b/config/application.rb index 24d5009745..fa5411b9a6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -43,7 +43,7 @@ module Openfoodweb # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. - # config.autoload_paths += %W(#{config.root}/extras) + config.autoload_paths += %W(#{config.root}/app/presenters) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. From fd0cc93e68fd60412f05c13fdfbb948036a0516e Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 22 Nov 2012 16:19:10 +1100 Subject: [PATCH 21/29] Add delete link --- app/assets/javascripts/admin/all.js | 1 + .../javascripts/admin/enterprise_fees.js | 13 +- app/assets/javascripts/angular.js | 158 ++++++++++++++++++ .../admin/enterprise_fees/index.html.haml | 18 +- 4 files changed, 171 insertions(+), 19 deletions(-) create mode 100644 app/assets/javascripts/angular.js diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 226359330f..e51c920bb8 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -7,6 +7,7 @@ //= require jquery //= require jquery_ujs +//= require angular //= require admin/spree_core //= require admin/spree_auth //= require admin/spree_promo diff --git a/app/assets/javascripts/admin/enterprise_fees.js b/app/assets/javascripts/admin/enterprise_fees.js index e2cbb28bbc..5e586ccdc3 100644 --- a/app/assets/javascripts/admin/enterprise_fees.js +++ b/app/assets/javascripts/admin/enterprise_fees.js @@ -5,9 +5,16 @@ function AdminEnterpriseFeesCtrl($scope, $http) { } - - - +angular.module('enterprise_fees', []) + .directive('spreeDeleteResource', function() { + return function(scope, element, attrs) { + if(scope.enterprise_fee.id) { + var url = "/admin/enterprise_fees/" + scope.enterprise_fee.id + var html = 'Delete Delete'; + element.append(html); + } + } + }); diff --git a/app/assets/javascripts/angular.js b/app/assets/javascripts/angular.js new file mode 100644 index 0000000000..accc48e2f2 --- /dev/null +++ b/app/assets/javascripts/angular.js @@ -0,0 +1,158 @@ +/* + AngularJS v1.0.2 + (c) 2010-2012 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(T,ba,p){'use strict';function m(b,a,c){var d;if(b)if(M(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==m)b.forEach(a,c);else if(I(b)&&wa(b.length))for(d=0;d=0&&b.splice(c,1);return a}function U(b,a){if(oa(b)||b&&b.$evalAsync&&b.$watch)throw A("Can't copy Window or Scope");if(a){if(b=== +a)throw A("Can't copy equivalent objects or arrays");if(K(b)){for(;a.length;)a.pop();for(var c=0;c2?ha.call(arguments,2):[];return M(a)&&!(a instanceof RegExp)?c.length? +function(){return arguments.length?a.apply(b,c.concat(ha.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function jc(b,a){var c=a;/^\$+/.test(b)?c=p:oa(a)?c="$WINDOW":a&&ba===a?c="$DOCUMENT":a&&a.$evalAsync&&a.$watch&&(c="$SCOPE");return c}function ca(b,a){return JSON.stringify(b,jc,a?" ":null)}function ob(b){return F(b)?JSON.parse(b):b}function Xa(b){b&&b.length!==0?(b=E(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1; +return b}function pa(b){b=y(b).clone();try{b.html("")}catch(a){}return y("
").append(b).html().match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+E(b)})}function Ya(b){var a={},c,d;m((b||"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),a[d]=u(c[1])?decodeURIComponent(c[1]):!0)});return a}function pb(b){var a=[];m(b,function(b,d){a.push(Za(d,!0)+(b===!0?"":"="+Za(b,!0)))});return a.length?a.join("&"):""}function $a(b){return Za(b,!0).replace(/%26/gi,"&").replace(/%3D/gi, +"=").replace(/%2B/gi,"+")}function Za(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(a?null:/%20/g,"+")}function kc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,i=["ng:app","ng-app","x-ng-app","data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;m(i,function(a){i[a]=!0;c(ba.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(m(b.querySelectorAll("."+a),c),m(b.querySelectorAll("."+a+"\\:"),c),m(b.querySelectorAll("["+ +a+"]"),c))});m(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):m(a.attributes,function(b){if(!e&&i[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}function qb(b,a){b=y(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");var c=rb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,i){a.$apply(function(){b.data("$injector",i);c(b)(a)})}]);return c}function ab(b,a){a=a||"_";return b.replace(lc, +function(b,d){return(d?a:"")+b.toLowerCase()})}function qa(b,a,c){if(!b)throw new A("Argument '"+(a||"?")+"' is "+(c||"required"));return b}function ra(b,a,c){c&&K(b)&&(b=b[b.length-1]);qa(M(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function mc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c, +d,e){return function(){b[e||"push"]([c,d,arguments]);return j}}if(!e)throw A("No module: "+d);var b=[],c=[],k=a("$injector","invoke"),j={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:k,run:function(a){c.push(a); +return this}};g&&k(g);return j})}})}function sb(b){return b.replace(nc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(oc,"Moz$1")}function bb(b,a){function c(){var e;for(var b=[this],c=a,i,f,h,k,j,l,n;b.length;){i=b.shift();f=0;for(h=i.length;f 
"+b;a.removeChild(a.firstChild);cb(this,a.childNodes);this.remove()}else cb(this,b)}function db(b){return b.cloneNode(!0)}function sa(b){tb(b);for(var a=0,b=b.childNodes||[];a +-1}function wb(b,a){a&&m(a.split(" "),function(a){b.className=Q((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+Q(a)+" "," "))})}function xb(b,a){a&&m(a.split(" "),function(a){if(!Ca(b,a))b.className=Q(b.className+" "+Q(a))})}function cb(b,a){if(a)for(var a=!a.nodeName&&u(a.length)&&!oa(a)?a:[a],c=0;c4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!"),V.length>20&&c.warn("Cookie '"+a+"' possibly not set or overflowed because too many cookies were already set ("+ +V.length+" > 20 )")}else{if(h.cookie!==s){s=h.cookie;d=s.split("; ");V={};for(f=0;f0&&(V[unescape(e.substring(0,g))]=unescape(e.substring(g+1)))}return V}};f.defer=function(a,b){var c;o++;c=l(function(){delete r[c];e(a)},b||0);r[c]=!0;return c};f.defer.cancel=function(a){return r[a]?(delete r[a],n(a),e(D),!0):!1}}function xc(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new wc(b,d,a,c)}]}function yc(){this.$get=function(){function b(b, +d){function e(a){if(a!=l){if(n){if(n==a)n=a.n}else n=a;g(a.n,a.p);g(a,l);l=a;l.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw A("cacheId "+b+" taken");var i=0,f=x({},d,{id:b}),h={},k=d&&d.capacity||Number.MAX_VALUE,j={},l=null,n=null;return a[b]={put:function(a,b){var c=j[a]||(j[a]={key:a});e(c);t(b)||(a in h||i++,h[a]=b,i>k&&this.remove(n.key))},get:function(a){var b=j[a];if(b)return e(b),h[a]},remove:function(a){var b=j[a];if(b==l)l=b.p;if(b==n)n=b.n;g(b.n,b.p);delete j[a]; +delete h[a];i--},removeAll:function(){h={};i=0;j={};l=n=null},destroy:function(){j=f=h=null;delete a[b]},info:function(){return x({},f,{size:i})}}}var a={};b.info=function(){var b={};m(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function zc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Cb(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: "; +this.directive=function f(d,e){F(d)?(qa(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];m(a[d],function(a){try{var f=b.invoke(a);if(M(f))f={compile:J(f)};else if(!f.compile&&f.link)f.compile=J(f.link);f.priority=f.priority||0;f.name=f.name||d;f.require=f.require||f.controller&&f.name;f.restrict=f.restrict||"A";e.push(f)}catch(g){c(g)}});return e}])),a[d].push(e)):m(d,nb(f));return this};this.$get=["$injector","$interpolate","$exceptionHandler", +"$http","$templateCache","$parse","$controller","$rootScope",function(b,h,k,j,l,n,r,o){function w(a,b,c){a instanceof y||(a=y(a));m(a,function(b,c){b.nodeType==3&&(a[c]=y(b).wrap("").parent()[0])});var d=v(a,b,a,c);return function(b,c){qa(b,"scope");var e=c?ua.clone.call(a):a;e.data("$scope",b);q(e,"ng-scope");c&&c(e,b);d&&d(b,e,e);return e}}function q(a,b){try{a.addClass(b)}catch(c){}}function v(a,b,c,d){function e(a,c,d,g){for(var k,h,n,j,o,l=0,r=0,q=f.length;lC.priority)break;if(X=C.scope)N("isolated scope",B,C,s),I(X)&&(q(s,"ng-isolate-scope"),B=C),q(s,"ng-scope"),v=v||C;H=C.name;if(X=C.controller)u=u||{},N("'"+H+"' controller",u[H],C,s),u[H]=C;if(X=C.transclude)N("transclusion",D,C,s),D=C,o=C.priority,X=="element"?(W=y(b),s=c.$$element=y("<\!-- "+H+": "+c[H]+" --\>"),b=s[0],Ga(e,y(W[0]),b),t=w(W,d,o)):(W=y(db(b)).contents(),s.html(""),t=w(W,d));if(X=C.template)if(N("template",z,C,s),z=C,X=Ia(X),C.replace){W=y("
"+Q(X)+"
").contents(); +b=W[0];if(W.length!=1||b.nodeType!==1)throw new A(g+X);Ga(e,s,b);H={$attr:{}};a=a.concat(Y(b,a.splice(E+1,a.length-(E+1)),H));L(c,H);G=a.length}else s.html(X);if(C.templateUrl)N("template",z,C,s),z=C,j=V(a.splice(E,a.length-E),j,s,c,e,C.replace,t),G=a.length;else if(C.compile)try{x=C.compile(s,c,t),M(x)?f(null,x):x&&f(x.pre,x.post)}catch(J){k(J,pa(s))}if(C.terminal)j.terminal=!0,o=Math.max(o,C.priority)}j.scope=v&&v.scope;j.transclude=D&&t;return j}function z(d,e,g,h){var j=!1;if(a.hasOwnProperty(e))for(var n, +e=b.get(e+c),o=0,l=e.length;on.priority)&&n.restrict.indexOf(g)!=-1)d.push(n),j=!0}catch(r){k(r)}return j}function L(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;m(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});m(b,function(b,f){f=="class"?(q(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):f=="style"?e.attr("style",e.attr("style")+";"+b):f.charAt(0)!="$"&&!a.hasOwnProperty(f)&&(a[f]=b,d[f]=c[f])})}function V(a,b,c,d,e, +f,k){var h=[],n,o,r=c[0],q=a.shift(),w=x({},q,{controller:null,templateUrl:null,transclude:null});c.html("");j.get(q.templateUrl,{cache:l}).success(function(j){var l,q,j=Ia(j);if(f){q=y("
"+Q(j)+"
").contents();l=q[0];if(q.length!=1||l.nodeType!==1)throw new A(g+j);j={$attr:{}};Ga(e,c,l);Y(l,a,j);L(d,j)}else l=r,c.html(j);a.unshift(w);n=B(a,c,d,k);for(o=v(c.contents(),k);h.length;){var aa=h.pop(),j=h.pop();q=h.pop();var s=h.pop(),m=l;q!==r&&(m=db(l),Ga(j,y(q),m));n(function(){b(o,s,m,e,aa)}, +s,m,e,aa)}h=null}).error(function(a,b,c,d){throw A("Failed to load template: "+d.url);});return function(a,c,d,e,f){h?(h.push(c),h.push(d),h.push(e),h.push(f)):n(function(){b(o,c,d,e,f)},c,d,e,f)}}function s(a,b){return b.priority-a.priority}function N(a,b,c,d){if(b)throw A("Multiple directives ["+b.name+", "+c.name+"] asking for "+a+" on: "+pa(d));}function H(a,b){var c=h(b,!0);c&&a.push({priority:0,compile:J(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);q(d.data("$binding",e), +"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function W(a,b,c,d){var e=h(c,!0);e&&b.push({priority:100,compile:J(function(a,b,c){b=c.$$observers||(c.$$observers={});d==="class"&&(e=h(c[d],!0));c[d]=p;(b[d]||(b[d]=[])).$$inter=!0;(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d,a)})})})}function Ga(a,b,c){var d=b[0],e=d.parentNode,f,g;if(a){f=0;for(g=a.length;f0){var f=N[0],e=f.text;if(e==a||e==b||e==c||e==d||!a&&!b&&!c&&!d)return f}return!1}function f(b,c,d,f){return(b=i(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),N.shift(),b):!1}function h(a){f(a)||e("is unexpected, expecting ["+a+"]",i())}function k(a,b){return function(c,d){return a(c,d,b)}}function j(a,b,c){return function(d,f){return b(d,f,a,c)}}function l(){for(var a=[];;)if(N.length>0&&!i("}",")",";","]")&&a.push(t()),!f(";"))return a.length==1?a[0]:function(b,c){for(var d, +f=0;f","<=",">="))a=j(a,b.fn,q());return a}function v(){for(var a=m(),b;b=f("*","/","%");)a=j(a,b.fn,m());return a}function m(){var a;return f("+")?B():(a=f("-"))?j(V,a.fn,m()):(a=f("!"))?k(a.fn,m()):B()}function B(){var a;if(f("("))a=t(),h(")");else if(f("["))a=z();else if(f("{"))a=L();else{var b=f();(a=b.fn)||e("not a primary expression",b)}for(var c;b=f("(","[",".");)b.text==="("?(a=y(a,c),c=null):b.text==="["?(c=a,a=ea(a)):b.text==="."?(c=a,a=u(a)):e("IMPOSSIBLE"); +return a}function z(){var a=[];if(g().text!="]"){do a.push(H());while(f(","))}h("]");return function(b,c){for(var d=[],f=0;f1;d++){var e=a.shift(),g= +b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=c}function gb(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,i=0;i7),hasEvent:function(c){if(c=="input"&&$==9)return!1;if(t(a[c])){var e=b.document.createElement("div");a[c]="on"+c in e}return a[c]},csp:!1}}]}function Uc(){this.$get=J(T)}function Nb(b){var a={},c,d,e;if(!b)return a;m(b.split("\n"),function(b){e=b.indexOf(":");c=E(Q(b.substr(0,e)));d=Q(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function Ob(b){var a=I(b)?b:p;return function(c){a||(a=Nb(b));return c?a[E(c)]||null:a}}function Pb(b,a,c){if(M(c))return c(b,a);m(c, +function(c){b=c(b,a)});return b}function Vc(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d=this.defaults={transformResponse:[function(d){F(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=ob(d,!0)));return d}],transformRequest:[function(a){return I(a)&&Ta.apply(a)!=="[object File]"?ca(a):a}],headers:{common:{Accept:"application/json, text/plain, */*","X-Requested-With":"XMLHttpRequest"},post:{"Content-Type":"application/json;charset=utf-8"},put:{"Content-Type":"application/json;charset=utf-8"}}}, +e=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,h,k,j){function l(a){function c(a){var b=x({},a,{data:Pb(a.data,a.headers,f)});return 200<=a.status&&a.status<300?b:k.reject(b)}a.method=la(a.method);var e=a.transformRequest||d.transformRequest,f=a.transformResponse||d.transformResponse,g=d.headers,g=x({"X-XSRF-TOKEN":b.cookies()["XSRF-TOKEN"]},g.common,g[E(a.method)],a.headers),e=Pb(a.data,Ob(g),e),h;t(a.data)&&delete g["Content-Type"]; +h=n(a,e,g);h=h.then(c,c);m(w,function(a){h=a(h)});h.success=function(b){h.then(function(c){b(c.data,c.status,c.headers,a)});return h};h.error=function(b){h.then(null,function(c){b(c.data,c.status,c.headers,a)});return h};return h}function n(b,c,d){function e(a,b,c){m&&(200<=a&&a<300?m.put(w,[a,b,Nb(c)]):m.remove(w));f(b,a,c);h.$apply()}function f(a,c,d){c=Math.max(c,0);(200<=c&&c<300?j.resolve:j.reject)({data:a,status:c,headers:Ob(d),config:b})}function i(){var a=Va(l.pendingRequests,b);a!==-1&&l.pendingRequests.splice(a, +1)}var j=k.defer(),n=j.promise,m,p,w=r(b.url,b.params);l.pendingRequests.push(b);n.then(i,i);b.cache&&b.method=="GET"&&(m=I(b.cache)?b.cache:o);if(m)if(p=m.get(w))if(p.then)return p.then(i,i),p;else K(p)?f(p[1],p[0],U(p[2])):f(p,200,{});else m.put(w,n);p||a(b.method,w,c,e,d,b.timeout,b.withCredentials);return n}function r(a,b){if(!b)return a;var c=[];fc(b,function(a,b){a==null||a==p||(I(a)&&(a=ca(a)),c.push(encodeURIComponent(b)+"="+encodeURIComponent(a)))});return a+(a.indexOf("?")==-1?"?":"&")+ +c.join("&")}var o=c("$http"),w=[];m(e,function(a){w.push(F(a)?j.get(a):j.invoke(a))});l.pendingRequests=[];(function(a){m(arguments,function(a){l[a]=function(b,c){return l(x(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){m(arguments,function(a){l[a]=function(b,c,d){return l(x(d||{},{method:a,url:b,data:c}))}})})("post","put");l.defaults=d;return l}]}function Wc(){this.$get=["$browser","$window","$document",function(b,a,c){return Xc(b,Yc,b.defer,a.angular.callbacks,c[0], +a.location.protocol.replace(":",""))}]}function Xc(b,a,c,d,e,g){function i(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;$?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=d;e.body.appendChild(c)}return function(e,h,k,j,l,n,r){function o(a,c,d,e){c=(h.match(Gb)||["",g])[1]=="file"?d?200:404:c;a(c==1223?204:c,d,e);b.$$completeOutstandingRequest(D)}b.$$incOutstandingRequestCount();h=h||b.url(); +if(E(e)=="jsonp"){var p="_"+(d.counter++).toString(36);d[p]=function(a){d[p].data=a};i(h.replace("JSON_CALLBACK","angular.callbacks."+p),function(){d[p].data?o(j,200,d[p].data):o(j,-2);delete d[p]})}else{var q=new a;q.open(e,h,!0);m(l,function(a,b){a&&q.setRequestHeader(b,a)});var v;q.onreadystatechange=function(){q.readyState==4&&o(j,v||q.status,q.responseText,q.getAllResponseHeaders())};if(r)q.withCredentials=!0;q.send(k||"");n>0&&c(function(){v=-1;q.abort()},n)}}}function Zc(){this.$get=function(){return{id:"en-us", +NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","), +SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function $c(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,h){var k=c.defer(),j=k.promise,l=u(h)&&!h,f=a.defer(function(){try{k.resolve(e())}catch(a){k.reject(a), +d(a)}l||b.$apply()},f),h=function(){delete g[j.$$timeoutId]};j.$$timeoutId=f;g[f]=k;j.then(h,h);return j}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Qb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Rb);a("date",Sb);a("filter",ad);a("json",bd);a("limitTo",cd);a("lowercase",dd);a("number", +Tb);a("orderBy",Ub);a("uppercase",ed)}function ad(){return function(b,a){if(!(b instanceof Array))return b;var c=[];c.check=function(a){for(var b=0;b-1;case "object":for(var c in a)if(c.charAt(0)!=="$"&&d(a[c],b))return!0;return!1;case "array":for(c=0;c=j+l)for(var k=i.length-j,n=0;n0||e>-c)e+=c;e===0&&c==-12&&(e= +12);return jb(e,a,d)}}function Ma(b,a){return function(c,d){var e=c["get"+b](),g=la(a?"SHORT"+b:b);return d[g][e]}}function Sb(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,i=0;b[9]&&(g=G(b[9]+b[10]),i=G(b[9]+b[11]));a.setUTCFullYear(G(b[1]),G(b[2])-1,G(b[3]));a.setUTCHours(G(b[4]||0)-g,G(b[5]||0)-i,G(b[6]||0),G(b[7]||0))}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;return function(c,e){var g="",i=[],f,h,e=e|| +"mediumDate",e=b.DATETIME_FORMATS[e]||e;F(c)&&(c=fd.test(c)?G(c):a(c));wa(c)&&(c=new Date(c));if(!na(c))return c;for(;e;)(h=gd.exec(e))?(i=i.concat(ha.call(h,1)),e=i.pop()):(i.push(e),e=null);m(i,function(a){f=hd[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function bd(){return function(b){return ca(b,!0)}}function cd(){return function(b,a){if(!(b instanceof Array))return b;var a=G(a),c=[],d,e;if(!b||!(b instanceof Array))return c;a>b.length?a=b.length:a< +-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;dl?(d.$setValidity("maxlength",!1),p):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(c);d.$formatters.push(c)}}function kb(b,a){b="ngClass"+b;return R(function(c,d,e){c.$watch(e[b],function(b,e){if(a===!0||c.$index%2===a)e&&b!==e&&(I(e)&&!K(e)&&(e=Ua(e,function(a,b){if(a)return b})),d.removeClass(K(e)?e.join(" "):e)),I(b)&&!K(b)&&(b=Ua(b, +function(a,b){if(a)return b})),b&&d.addClass(K(b)?b.join(" "):b)},!0)})}var E=function(b){return F(b)?b.toLowerCase():b},la=function(b){return F(b)?b.toUpperCase():b},A=T.Error,$=G((/msie (\d+)/.exec(E(navigator.userAgent))||[])[1]),y,ia,ha=[].slice,Sa=[].push,Ta=Object.prototype.toString,Zb=T.angular||(T.angular={}),ta,Db,Z=["0","0","0"];D.$inject=[];ma.$inject=[];Db=$<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?la(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName? +b.nodeName:b[0].nodeName};var lc=/[A-Z]/g,id={full:"1.0.2",major:1,minor:0,dot:2,codeName:"debilitating-awesomeness"},Ba=P.cache={},Aa=P.expando="ng-"+(new Date).getTime(),pc=1,$b=T.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},eb=T.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},nc=/([\:\-\_]+(.))/g,oc=/^moz([A-Z])/,ua=P.prototype={ready:function(b){function a(){c|| +(c=!0,b())}var c=!1;this.bind("DOMContentLoaded",a);P(T).bind("load",a)},toString:function(){var b=[];m(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?y(this[b]):y(this[this.length+b])},length:0,push:Sa,sort:[].sort,splice:[].splice},Ea={};m("multiple,selected,checked,disabled,readOnly,required".split(","),function(b){Ea[E(b)]=b});var Ab={};m("input,select,option,textarea,button,form".split(","),function(b){Ab[la(b)]=!0});m({data:vb,inheritedData:Da,scope:function(b){return Da(b, +"$scope")},controller:yb,injector:function(b){return Da(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ca,css:function(b,a,c){a=sb(a);if(u(c))b.style[a]=c;else{var d;$<=8&&(d=b.currentStyle&&b.currentStyle[a],d===""&&(d="auto"));d=d||b.style[a];$<=8&&(d=d===""?p:d);return d}},attr:function(b,a,c){var d=E(a);if(Ea[d])if(u(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||D).specified?d:p;else if(u(c))b.setAttribute(a, +c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?p:b},prop:function(b,a,c){if(u(c))b[a]=c;else return b[a]},text:x($<9?function(b,a){if(b.nodeType==1){if(t(a))return b.innerText;b.innerText=a}else{if(t(a))return b.nodeValue;b.nodeValue=a}}:function(b,a){if(t(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(t(a))return b.value;b.value=a},html:function(b,a){if(t(a))return b.innerHTML;for(var c=0,d=b.childNodes;c":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)& +e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Lc={n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'},ib={},Yc=T.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw new A("This browser does not support XMLHttpRequest.");};Qb.$inject=["$provide"];Rb.$inject=["$locale"];Tb.$inject=["$locale"]; +var Wb=".",hd={yyyy:O("FullYear",4),yy:O("FullYear",2,0,!0),y:O("FullYear",1),MMMM:Ma("Month"),MMM:Ma("Month",!0),MM:O("Month",2,1),M:O("Month",1,1),dd:O("Date",2),d:O("Date",1),HH:O("Hours",2),H:O("Hours",1),hh:O("Hours",2,-12),h:O("Hours",1,-12),mm:O("Minutes",2),m:O("Minutes",1),ss:O("Seconds",2),s:O("Seconds",1),EEEE:Ma("Day"),EEE:Ma("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=a.getTimezoneOffset();return jb(a/60,2)+jb(Math.abs(a%60),2)}},gd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, +fd=/^\d+$/;Sb.$inject=["$locale"];var dd=J(E),ed=J(la);Ub.$inject=["$parse"];var jd=J({restrict:"E",compile:function(a,c){c.href||c.$set("href","");return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),lb={};m(Ea,function(a,c){var d=fa("ng-"+c);lb[d]=function(){return{priority:100,compile:function(){return function(a,g,i){a.$watch(i[d],function(a){i.$set(c,!!a)})}}}}});m(["src","href"],function(a){var c=fa("ng-"+a);lb[c]=function(){return{priority:99,link:function(d, +e,g){g.$observe(c,function(c){g.$set(a,c);$&&e.prop(a,c)})}}}});var Pa={$addControl:D,$removeControl:D,$setValidity:D,$setDirty:D};Xb.$inject=["$element","$attrs","$scope"];var Sa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E",controller:Xb,compile:function(){return{pre:function(a,d,i,f){if(!i.action){var h=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};$b(d[0],"submit",h);d.bind("$destroy",function(){c(function(){eb(d[0],"submit",h)},0,!1)})}var k=d.parent().controller("form"), +j=i.name||i.ngForm;j&&(a[j]=f);k&&d.bind("$destroy",function(){k.$removeControl(f);j&&(a[j]=p);x(f,Pa)})}}}};return a?x(U(d),{restrict:"EAC"}):d}]},kd=Sa(),ld=Sa(!0),md=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,nd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,od=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,bc={text:Ra,number:function(a,c,d,e,g,i){Ra(a,c,d,e,g,i);e.$parsers.push(function(a){var c=S(a);return c||od.test(a)?(e.$setValidity("number",!0),a===""? +null:c?a:parseFloat(a)):(e.$setValidity("number",!1),p)});e.$formatters.push(function(a){return S(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!S(a)&&ah?(e.$setValidity("max",!1),p):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return S(a)||wa(a)?(e.$setValidity("number", +!0),a):(e.$setValidity("number",!1),p)})},url:function(a,c,d,e,g,i){Ra(a,c,d,e,g,i);a=function(a){return S(a)||md.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,i){Ra(a,c,d,e,g,i);a=function(a){return S(a)||nd.test(a)?(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){t(d.name)&&c.attr("name",xa());c.bind("click",function(){c[0].checked&& +a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,i=d.ngFalseValue;F(g)||(g=!0);F(i)||(i=!1);c.bind("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:i})},hidden:D,button:D,submit:D,reset:D},cc=["$browser","$sniffer", +function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,i){i&&(bc[E(g.type)]||bc.text)(d,e,g,i,c,a)}}}],Oa="ng-valid",Na="ng-invalid",Qa="ng-pristine",Yb="ng-dirty",pd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function i(a,c){c=c?"-"+ab(c,"-"):"";e.removeClass((a?Na:Oa)+c).addClass((a?Oa:Na)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid= +!0;this.$invalid=!1;this.$name=d.name;var g=g(d.ngModel),f=g.assign;if(!f)throw A(Eb+d.ngModel+" ("+pa(e)+")");this.$render=D;var h=e.inheritedData("$formController")||Pa,k=0,j=this.$error={};e.addClass(Qa);i(!0);this.$setValidity=function(a,c){if(j[a]!==!c){if(c){if(j[a]&&k--,!k)i(!0),this.$valid=!0,this.$invalid=!1}else i(!1),this.$invalid=!0,this.$valid=!1,k++;j[a]=!c;i(c,a);h.$setValidity(a,c,this)}};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine= +!1,e.removeClass(Qa).addClass(Yb),h.$setDirty();m(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,f(a,d),m(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};var l=this;a.$watch(g,function(a){if(l.$modelValue!==a){var c=l.$formatters,d=c.length;for(l.$modelValue=a;d--;)a=c[d](a);if(l.$viewValue!==a)l.$viewValue=a,l.$render()}})}],qd=function(){return{require:["ngModel","^?form"],controller:pd,link:function(a,c,d,e){var g=e[0],i=e[1]||Pa;i.$addControl(g); +c.bind("$destroy",function(){i.$removeControl(g)})}}},rd=J({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),dc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&(S(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},sd=function(){return{require:"ngModel", +link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&m(a.split(g),function(a){a&&c.push(Q(a))});return c});e.$formatters.push(function(a){return K(a)?a.join(", "):p})}}},td=/^(true|false|\d+)$/,ud=function(){return{priority:100,compile:function(a,c){return td.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},vd=R(function(a,c,d){c.addClass("ng-binding").data("$binding", +d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==p?"":a)})}),wd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],xd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],yd=kb("",!0),zd=kb("Odd",0),Ad=kb("Even",1),Bd=R({compile:function(a,c){c.$set("ngCloak",p); +a.removeClass("ng-cloak")}}),Cd=[function(){return{scope:!0,controller:"@"}}],Dd=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],ec={};m("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave".split(" "),function(a){var c=fa("ng-"+a);ec[c]=["$parse",function(d){return function(e,g,i){var f=d(i[c]);g.bind(E(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Ed=R(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}), +Fd=["$http","$templateCache","$anchorScroll","$compile",function(a,c,d,e){return{restrict:"ECA",terminal:!0,compile:function(g,i){var f=i.ngInclude||i.src,h=i.onload||"",k=i.autoscroll;return function(g,i){var n=0,m,o=function(){m&&(m.$destroy(),m=null);i.html("")};g.$watch(f,function(f){var q=++n;f?a.get(f,{cache:c}).success(function(a){q===n&&(m&&m.$destroy(),m=g.$new(),i.html(a),e(i.contents())(m),u(k)&&(!k||g.$eval(k))&&d(),m.$emit("$includeContentLoaded"),g.$eval(h))}).error(function(){q===n&& +o()}):o()})}}}}],Gd=R({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Hd=R({terminal:!0,priority:1E3}),Id=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,i){var f=i.count,h=g.attr(i.$attr.when),k=i.offset||0,j=e.$eval(h),l={},n=c.startSymbol(),r=c.endSymbol();m(j,function(a,e){l[e]=c(a.replace(d,n+f+"-"+k+r))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(j[c]||(c=a.pluralCat(c-k)),l[c](e,g,!0))},function(a){g.text(a)})}}}], +Jd=R({transclude:"element",priority:1E3,terminal:!0,compile:function(a,c,d){return function(a,c,i){var f=i.ngRepeat,i=f.match(/^\s*(.+)\s+in\s+(.*)\s*$/),h,k,j;if(!i)throw A("Expected ngRepeat in form of '_item_ in _collection_' but got '"+f+"'.");f=i[1];h=i[2];i=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!i)throw A("'item' in 'item in collection' should be identifier or (key, value) but got '"+f+"'.");k=i[3]||i[1];j=i[2];var l=new fb;a.$watch(function(a){var e,f,i=a.$eval(h),m=hc(i, +!0),p,y=new fb,B,z,t,u,s=c;if(K(i))t=i||[];else{t=[];for(B in i)i.hasOwnProperty(B)&&B.charAt(0)!="$"&&t.push(B);t.sort()}e=0;for(f=t.length;ex;)u.pop().element.remove()}for(;v.length>w;)v.pop()[0].element.remove()}var i;if(!(i=w.match(d)))throw A("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '"+w+"'.");var j=c(i[2]||i[1]),k=i[4]||i[6],l=i[5],m=c(i[3]||""),n=c(i[2]?i[1]:k),r=c(i[7]),v=[[{element:f,label:""}]];q&&(a(q)(e),q.removeClass("ng-scope"), +q.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=r(e)||[],d={},h,i,j,m,q,s;if(o){i=[];m=0;for(s=v.length;m@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}'); diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index c5af08e0a3..67c10480f1 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -1,7 +1,4 @@ -- content_for :head do - = javascript_include_tag "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js" - -= angular_form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path, :html => {'ng-app' => '', 'ng-controller' => 'AdminEnterpriseFeesCtrl'} do |enterprise_fee_set_form| += angular_form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path, :html => {'ng-app' => 'enterprise_fees', 'ng-controller' => 'AdminEnterpriseFeesCtrl'} do |enterprise_fee_set_form| - if @enterprise_fee_set.errors.present? %h2 Errors %ul @@ -28,22 +25,11 @@ %td= f.angular_text_field :name %td= f.angular_collection_select :calculator_type, @calculators, :name, :description, 'enterprise_fee.calculator_type', {:class => 'calculator_type'} %td{'ng-bind-html-unsafe' => 'enterprise_fee.calculator_settings'} - %td Delete + %td{'spree-delete-resource' => "1"} %tr %td --- - / -- Manual HTML / form_tag - %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees'} - %td - %select{:id => 'enterprise_fee_set_collection_attributes_{{ $index }}_enterprise_id', :name => "enterprise_fee_set[collection_attributes][{{ $index }}][enterprise_id]", 'ng-model' => 'enterprise_fee'} - %option{:value => ""} - - Enterprise.all.each do |enterprise| - %option{:value => enterprise.id, 'ng-selected' => "enterprise_fee.enterprise_id == #{enterprise.id}"}= enterprise.name - %td= select_tag :fee_type, angular_options_for_select(enterprise_fee_type_options, 'enterprise_fee.fee_type') - %td= text_field_tag 'enterprise_fee_set[collection_attributes][{{ $index }}][name]', '{{ enterprise_fee.name }}', :id => 'enterprise_fee_set_collection_attributes_{{ $index }}_name' - %td {{ enterprise_fee.calculator_type }} - / -- Plain old Rails = enterprise_fee_set_form.fields_for :collection do |f| - enterprise_fee = f.object From 52ebe2c495543856b84919160b062d9fe03d711d Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 22 Nov 2012 16:38:25 +1100 Subject: [PATCH 22/29] Provide proper names for angular select fields, add angular hidden field --- app/helpers/angular_form_builder.rb | 26 +++++++++++++++---- .../admin/enterprise_fees/index.html.haml | 21 +++------------ 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb index 3d51291cbc..8444928f91 100644 --- a/app/helpers/angular_form_builder.rb +++ b/app/helpers/angular_form_builder.rb @@ -13,19 +13,35 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder # @fields_for_record_name --> :collection # @object.send(@fields_for_record_name).first.class.to_s.underscore --> enterprise_fee - name = "#{@object_name}[#{@fields_for_record_name}_attributes][{{ $index }}][#{method}]" - id = "#{@object_name}_#{@fields_for_record_name}_attributes_{{ $index }}_#{method}" value = "{{ #{@object.send(@fields_for_record_name).first.class.to_s.underscore}.#{method} }}" - @template.text_field_tag name, value, :id => id + @template.text_field_tag angular_name(method), value, :id => angular_id(method) + end + + def angular_hidden_field(method, options = {}) + value = "{{ #{@object.send(@fields_for_record_name).first.class.to_s.underscore}.#{method} }}" + + @template.hidden_field_tag angular_name(method), value, :id => angular_id(method) end def angular_select(method, choices, angular_field, options = {}) - @template.select_tag method, @template.angular_options_for_select(choices, angular_field), options.reverse_merge!({'ng-model' => angular_field}) + options.reverse_merge!({'id' => angular_id(method)}) + + @template.select_tag angular_name(method), @template.angular_options_for_select(choices, angular_field), options end def angular_collection_select(method, collection, value_method, text_method, angular_field, options = {}) - @template.select_tag method, @template.angular_options_from_collection_for_select(collection, value_method, text_method, angular_field), options.reverse_merge!({'ng-model' => angular_field}) + options.reverse_merge!({'id' => angular_id(method)}) + + @template.select_tag angular_name(method), @template.angular_options_from_collection_for_select(collection, value_method, text_method, angular_field), options end + private + def angular_name(method) + "#{@object_name}[#{@fields_for_record_name}_attributes][{{ $index }}][#{method}]" + end + + def angular_id(method) + "#{@object_name}_#{@fields_for_record_name}_attributes_{{ $index }}_#{method}" + end end diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index 67c10480f1..0ce3168b53 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -20,28 +20,13 @@ / -- Finished product = enterprise_fee_set_form.angular_fields_for :collection do |f| %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees | filter:query'} - %td= f.angular_collection_select :enterprise_id, Enterprise.all, :id, :name, 'enterprise_fee.enterprise_id', :include_blank => true + %td + = f.angular_hidden_field :id + = f.angular_collection_select :enterprise_id, Enterprise.all, :id, :name, 'enterprise_fee.enterprise_id', :include_blank => true %td= f.angular_select :fee_type, enterprise_fee_type_options, 'enterprise_fee.fee_type' %td= f.angular_text_field :name %td= f.angular_collection_select :calculator_type, @calculators, :name, :description, 'enterprise_fee.calculator_type', {:class => 'calculator_type'} %td{'ng-bind-html-unsafe' => 'enterprise_fee.calculator_settings'} %td{'spree-delete-resource' => "1"} - %tr - %td --- - - / -- Plain old Rails - = enterprise_fee_set_form.fields_for :collection do |f| - - enterprise_fee = f.object - %tr - %td= f.collection_select :enterprise_id, Enterprise.all, :id, :name, :include_blank => true - %td= f.select :fee_type, enterprise_fee_type_options - %td= f.text_field :name - %td= f.collection_select :calculator_type, @calculators, :name, :description, {}, {:class => 'calculator_type'} - %td - - if !enterprise_fee.new_record? - .calculator-settings - = f.fields_for :calculator do |calculator_form| - = preference_fields(enterprise_fee.calculator, calculator_form) - %td= link_to_delete enterprise_fee unless enterprise_fee.new_record? = enterprise_fee_set_form.submit 'Update' From 5e0a5654ffcd7d0138ddf03251e2ba6792c61ad2 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 23 Nov 2012 17:02:58 +1100 Subject: [PATCH 23/29] Correctly index calculator fields. Adds ng-bind-html-unsafe-compiled. --- app/assets/javascripts/admin/enterprise_fees.js | 8 +++++++- .../admin/enterprise_fees_controller.rb | 2 +- app/models/model_set.rb | 3 +-- app/presenters/enterprise_fee_presenter.rb | 9 ++++----- .../enterprise_fees/_calculator_settings.html.haml | 14 ++++++++------ app/views/admin/enterprise_fees/index.html.haml | 3 +-- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/app/assets/javascripts/admin/enterprise_fees.js b/app/assets/javascripts/admin/enterprise_fees.js index 5e586ccdc3..a497c8cf96 100644 --- a/app/assets/javascripts/admin/enterprise_fees.js +++ b/app/assets/javascripts/admin/enterprise_fees.js @@ -4,8 +4,14 @@ function AdminEnterpriseFeesCtrl($scope, $http) { }); } - angular.module('enterprise_fees', []) + .directive('ngBindHtmlUnsafeCompiled', function($compile) { + return function(scope, element, attrs) { + scope.$watch(attrs.ngBindHtmlUnsafeCompiled, function(value) { + element.html($compile(value)(scope)); + }); + } + }) .directive('spreeDeleteResource', function() { return function(scope, element, attrs) { if(scope.enterprise_fee.id) { diff --git a/app/controllers/admin/enterprise_fees_controller.rb b/app/controllers/admin/enterprise_fees_controller.rb index 2958ab6c73..3f1ec8e265 100644 --- a/app/controllers/admin/enterprise_fees_controller.rb +++ b/app/controllers/admin/enterprise_fees_controller.rb @@ -6,7 +6,7 @@ module Admin def index respond_to do |format| format.html - format.json { @presented_collection = @collection.map { |ef| EnterpriseFeePresenter.new(self, ef) } } + format.json { @presented_collection = @collection.each_with_index.map { |ef, i| EnterpriseFeePresenter.new(self, ef, i) } } end end diff --git a/app/models/model_set.rb b/app/models/model_set.rb index 778a148b82..0452b0bafb 100644 --- a/app/models/model_set.rb +++ b/app/models/model_set.rb @@ -5,8 +5,7 @@ class ModelSet attr_accessor :collection - - def initialize(klass, collection, reject_if, attributes={}) + def initialize(klass, collection, reject_if=nil, attributes={}) @klass, @collection, @reject_if = klass, collection, reject_if attributes.each do |name, value| diff --git a/app/presenters/enterprise_fee_presenter.rb b/app/presenters/enterprise_fee_presenter.rb index bbecfc0f4f..a0a6d8460a 100644 --- a/app/presenters/enterprise_fee_presenter.rb +++ b/app/presenters/enterprise_fee_presenter.rb @@ -1,7 +1,6 @@ class EnterpriseFeePresenter - def initialize(controller, enterprise_fee) - @controller = controller - @enterprise_fee = enterprise_fee + def initialize(controller, enterprise_fee, index) + @controller, @enterprise_fee, @index = controller, enterprise_fee, index end delegate :id, :enterprise_id, :fee_type, :name, :calculator_type, :to => :enterprise_fee @@ -23,10 +22,10 @@ class EnterpriseFeePresenter result = nil @controller.send(:with_format, :html) do - result = @controller.render_to_string :partial => 'admin/enterprise_fees/calculator_settings', :locals => {:enterprise_fee => @enterprise_fee} + result = @controller.render_to_string :partial => 'admin/enterprise_fees/calculator_settings', :locals => {:enterprise_fee => @enterprise_fee, :index => @index} end - result + result.gsub('[0]', '[{{ $index }}]').gsub('_0_', '_{{ $index }}_') end end diff --git a/app/views/admin/enterprise_fees/_calculator_settings.html.haml b/app/views/admin/enterprise_fees/_calculator_settings.html.haml index 8a81602b71..31488703ac 100644 --- a/app/views/admin/enterprise_fees/_calculator_settings.html.haml +++ b/app/views/admin/enterprise_fees/_calculator_settings.html.haml @@ -1,9 +1,11 @@ -# Render only the calculator settings and not the surrounding form +- enterprise_fee_set = ModelSet.new(EnterpriseFee, EnterpriseFee.where(:id => enterprise_fee.id)) - form = nil -- form_for enterprise_fee, :url => '' do |f| - - form = capture do - - if !enterprise_fee.new_record? - .calculator-settings - = f.fields_for :calculator do |calculator_form| - = preference_fields(enterprise_fee.calculator, calculator_form) += form_for enterprise_fee_set, :as => :enterprise_fee_set, :url => '' do |form| + = form.fields_for :collection do |f| + - form = capture do + - if !enterprise_fee.new_record? + .calculator-settings + = f.fields_for :calculator do |calculator_form| + = preference_fields(enterprise_fee.calculator, calculator_form) = form diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index 0ce3168b53..13a1f6f25c 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -17,7 +17,6 @@ %th Calculator values %th %tbody - / -- Finished product = enterprise_fee_set_form.angular_fields_for :collection do |f| %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees | filter:query'} %td @@ -26,7 +25,7 @@ %td= f.angular_select :fee_type, enterprise_fee_type_options, 'enterprise_fee.fee_type' %td= f.angular_text_field :name %td= f.angular_collection_select :calculator_type, @calculators, :name, :description, 'enterprise_fee.calculator_type', {:class => 'calculator_type'} - %td{'ng-bind-html-unsafe' => 'enterprise_fee.calculator_settings'} + %td{'ng-bind-html-unsafe-compiled' => 'enterprise_fee.calculator_settings'} %td{'spree-delete-resource' => "1"} = enterprise_fee_set_form.submit 'Update' From c46b8585638f6bbfdef6ba48d4bd99a1e9d37373 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 23 Nov 2012 17:20:52 +1100 Subject: [PATCH 24/29] Don't render teh bits we dont wants --- .../enterprise_fees/_calculator_settings.html.haml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/views/admin/enterprise_fees/_calculator_settings.html.haml b/app/views/admin/enterprise_fees/_calculator_settings.html.haml index 31488703ac..45e2a7c394 100644 --- a/app/views/admin/enterprise_fees/_calculator_settings.html.haml +++ b/app/views/admin/enterprise_fees/_calculator_settings.html.haml @@ -1,11 +1,12 @@ -# Render only the calculator settings and not the surrounding form - enterprise_fee_set = ModelSet.new(EnterpriseFee, EnterpriseFee.where(:id => enterprise_fee.id)) -- form = nil -= form_for enterprise_fee_set, :as => :enterprise_fee_set, :url => '' do |form| - = form.fields_for :collection do |f| - - form = capture do +- calculator_form_string = nil +- form_for enterprise_fee_set, :as => :enterprise_fee_set, :url => '' do |form| + - form.fields_for :collection do |f| + - calculator_form_string = capture do - if !enterprise_fee.new_record? .calculator-settings = f.fields_for :calculator do |calculator_form| = preference_fields(enterprise_fee.calculator, calculator_form) -= form + += calculator_form_string From 5ae13d61564cf031088e01af5b3024e48f57fe29 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 23 Nov 2012 17:21:46 +1100 Subject: [PATCH 25/29] Order all the fees --- app/controllers/admin/enterprise_fees_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/enterprise_fees_controller.rb b/app/controllers/admin/enterprise_fees_controller.rb index 3f1ec8e265..ca48ac7dc5 100644 --- a/app/controllers/admin/enterprise_fees_controller.rb +++ b/app/controllers/admin/enterprise_fees_controller.rb @@ -30,7 +30,7 @@ module Admin end def collection - super + (1..3).map { EnterpriseFee.new } + super.order('enterprise_id', 'fee_type', 'name') + (1..3).map { EnterpriseFee.new } end end From d5027053625ea57e3ca55fd181c2c5924ccaa6c4 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Sat, 24 Nov 2012 07:50:09 +1100 Subject: [PATCH 26/29] Raise an error when using unsupported nested angular_fields_for --- app/helpers/angular_form_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb index 8444928f91..14f8e28e02 100644 --- a/app/helpers/angular_form_builder.rb +++ b/app/helpers/angular_form_builder.rb @@ -2,7 +2,7 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder # TODO: Use ng_ prefix, like ng_fields_for def angular_fields_for(record_name, *args, &block) - # TODO: Handle nested angular_fields_for + raise "Nested angular_fields_for is not yet supported" if @fields_for_record_name.present @fields_for_record_name = record_name block.call self @fields_for_record_name = nil From 59af447739f161d8ba33db47ac1ff8c96f941fb1 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 26 Nov 2012 09:44:42 +1100 Subject: [PATCH 27/29] Fix ? --- app/helpers/angular_form_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb index 14f8e28e02..4b60706b36 100644 --- a/app/helpers/angular_form_builder.rb +++ b/app/helpers/angular_form_builder.rb @@ -2,7 +2,7 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder # TODO: Use ng_ prefix, like ng_fields_for def angular_fields_for(record_name, *args, &block) - raise "Nested angular_fields_for is not yet supported" if @fields_for_record_name.present + raise "Nested angular_fields_for is not yet supported" if @fields_for_record_name.present? @fields_for_record_name = record_name block.call self @fields_for_record_name = nil From 6106f1b46987bcd72a6f6709ee85e9f60ee8a0c6 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 26 Nov 2012 09:45:06 +1100 Subject: [PATCH 28/29] Calculator type change works, enterprise fee tests pass --- .../javascripts/admin/enterprise_fees.js | 57 ++++++++++--------- .../admin/enterprise_fees/index.html.haml | 2 +- spec/requests/admin/enterprise_fees_spec.rb | 6 +- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/app/assets/javascripts/admin/enterprise_fees.js b/app/assets/javascripts/admin/enterprise_fees.js index a497c8cf96..e73ff0dfcf 100644 --- a/app/assets/javascripts/admin/enterprise_fees.js +++ b/app/assets/javascripts/admin/enterprise_fees.js @@ -1,9 +1,16 @@ function AdminEnterpriseFeesCtrl($scope, $http) { $http.get('/admin/enterprise_fees.json').success(function(data) { $scope.enterprise_fees = data; + + // TODO: Angular 1.1.0 will have a means to reset a form to its pristine state, which + // would avoid the need to save off original calculator types for comparison. + for(i in $scope.enterprise_fees) { + $scope.enterprise_fees[i].orig_calculator_type = $scope.enterprise_fees[i].calculator_type; + } }); } + angular.module('enterprise_fees', []) .directive('ngBindHtmlUnsafeCompiled', function($compile) { return function(scope, element, attrs) { @@ -20,36 +27,30 @@ angular.module('enterprise_fees', []) element.append(html); } } - }); + }) + .directive('spreeEnsureCalculatorPreferencesMatchType', function() { + // Hide calculator preference fields when calculator type changed + // Fixes 'Enterprise fee is not found' error when changing calculator type + // See spree/core/app/assets/javascripts/admin/calculator.js + // Note: For some reason, DOM --> model bindings aren't working here, so + // we use element.val() instead of querying the model itself. + return function(scope, element, attrs) { + scope.$watch(function(scope) { + //return scope.enterprise_fee.calculator_type; + return element.val(); + }, function(value) { + var settings = element.parent().parent().find("div.calculator-settings"); - -/* -// Hide calculator preference fields when calculator type changed -// Fixes 'Enterprise fee is not found' error when changing calculator type -// See spree/core/app/assets/javascripts/admin/calculator.js - -$(document).ready(function() { - // Store original value - $("select.calculator_type").each(function(i, ct) { - ct = $(ct); - ct.data('original-value', ct.attr('value')); - }); - - // Hide and disable calculator fields when calculator type is changed - $("select.calculator_type").change(function() { - var ct = $(this); - var cs = ct.parent().parent().find("div.calculator-settings"); - - if(ct.attr('value') == ct.data('original-value')) { - cs.show(); - cs.find("input").prop("disabled", false); - - } else { - cs.hide(); - cs.find("input").prop("disabled", true); + // scope.enterprise_fee.calculator_type == scope.enterprise_fee.orig_calculator_type + if(element.val() == scope.enterprise_fee.orig_calculator_type) { + settings.show(); + settings.find("input").prop("disabled", false); + } else { + settings.hide(); + settings.find("input").prop("disabled", true); + } + }); } }); -}); -*/ \ No newline at end of file diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index 13a1f6f25c..e2c5d4e854 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -24,7 +24,7 @@ = f.angular_collection_select :enterprise_id, Enterprise.all, :id, :name, 'enterprise_fee.enterprise_id', :include_blank => true %td= f.angular_select :fee_type, enterprise_fee_type_options, 'enterprise_fee.fee_type' %td= f.angular_text_field :name - %td= f.angular_collection_select :calculator_type, @calculators, :name, :description, 'enterprise_fee.calculator_type', {:class => 'calculator_type'} + %td= f.angular_collection_select :calculator_type, @calculators, :name, :description, 'enterprise_fee.calculator_type', {'class' => 'calculator_type', 'ng-model' => 'calculatorType', 'spree-ensure-calculator-preferences-match-type' => "1"} %td{'ng-bind-html-unsafe-compiled' => 'enterprise_fee.calculator_settings'} %td{'spree-delete-resource' => "1"} diff --git a/spec/requests/admin/enterprise_fees_spec.rb b/spec/requests/admin/enterprise_fees_spec.rb index 90f7f065d3..8af01b149f 100644 --- a/spec/requests/admin/enterprise_fees_spec.rb +++ b/spec/requests/admin/enterprise_fees_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature %q{ As an administrator I want to manage enterprise fees -} do +}, js: true do include AuthenticationWorkflow include WebHelper @@ -49,7 +49,7 @@ feature %q{ page.should have_selector "#enterprise_fee_set_collection_attributes_0_calculator_attributes_preferred_flat_percent[value='12.34']" end - scenario "editing an enterprise fee", js: true do + scenario "editing an enterprise fee" do # Given an enterprise fee fee = create(:enterprise_fee) create(:enterprise, name: 'Foo') @@ -73,7 +73,7 @@ feature %q{ page.should have_selector "option[selected]", text: 'Flat Percent' end - scenario "deleting an enterprise fee", js: true do + scenario "deleting an enterprise fee" do # Given an enterprise fee fee = create(:enterprise_fee) From 7c5dcd6a90af83662415720f41ff8fbc0b22f228 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 26 Nov 2012 10:03:20 +1100 Subject: [PATCH 29/29] Change angular_ prefix to ng_ --- app/helpers/angular_form_builder.rb | 18 ++++++++---------- app/helpers/angular_form_helper.rb | 6 +++--- app/helpers/application_helper.rb | 2 +- .../admin/enterprise_fees/index.html.haml | 14 +++++++------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/app/helpers/angular_form_builder.rb b/app/helpers/angular_form_builder.rb index 4b60706b36..5d913eb291 100644 --- a/app/helpers/angular_form_builder.rb +++ b/app/helpers/angular_form_builder.rb @@ -1,14 +1,12 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder - # TODO: Use ng_ prefix, like ng_fields_for - - def angular_fields_for(record_name, *args, &block) - raise "Nested angular_fields_for is not yet supported" if @fields_for_record_name.present? + def ng_fields_for(record_name, *args, &block) + raise "Nested ng_fields_for is not yet supported" if @fields_for_record_name.present? @fields_for_record_name = record_name block.call self @fields_for_record_name = nil end - def angular_text_field(method, options = {}) + def ng_text_field(method, options = {}) # @object_name --> "enterprise_fee_set" # @fields_for_record_name --> :collection # @object.send(@fields_for_record_name).first.class.to_s.underscore --> enterprise_fee @@ -18,22 +16,22 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder @template.text_field_tag angular_name(method), value, :id => angular_id(method) end - def angular_hidden_field(method, options = {}) + def ng_hidden_field(method, options = {}) value = "{{ #{@object.send(@fields_for_record_name).first.class.to_s.underscore}.#{method} }}" @template.hidden_field_tag angular_name(method), value, :id => angular_id(method) end - def angular_select(method, choices, angular_field, options = {}) + def ng_select(method, choices, angular_field, options = {}) options.reverse_merge!({'id' => angular_id(method)}) - @template.select_tag angular_name(method), @template.angular_options_for_select(choices, angular_field), options + @template.select_tag angular_name(method), @template.ng_options_for_select(choices, angular_field), options end - def angular_collection_select(method, collection, value_method, text_method, angular_field, options = {}) + def ng_collection_select(method, collection, value_method, text_method, angular_field, options = {}) options.reverse_merge!({'id' => angular_id(method)}) - @template.select_tag angular_name(method), @template.angular_options_from_collection_for_select(collection, value_method, text_method, angular_field), options + @template.select_tag angular_name(method), @template.ng_options_from_collection_for_select(collection, value_method, text_method, angular_field), options end private diff --git a/app/helpers/angular_form_helper.rb b/app/helpers/angular_form_helper.rb index 9116d403fa..a7fd6e0e28 100644 --- a/app/helpers/angular_form_helper.rb +++ b/app/helpers/angular_form_helper.rb @@ -1,5 +1,5 @@ module AngularFormHelper - def angular_options_for_select(container, angular_field=nil) + def ng_options_for_select(container, angular_field=nil) return container if String === container container.map do |element| @@ -10,12 +10,12 @@ module AngularFormHelper end.join("\n").html_safe end - def angular_options_from_collection_for_select(collection, value_method, text_method, angular_field) + def ng_options_from_collection_for_select(collection, value_method, text_method, angular_field) options = collection.map do |element| [element.send(text_method), element.send(value_method)] end - angular_options_for_select(options, angular_field) + ng_options_for_select(options, angular_field) end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5035bf8809..794aee744b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -6,7 +6,7 @@ module ApplicationHelper end - def angular_form_for(name, *args, &block) + def ng_form_for(name, *args, &block) options = args.extract_options! form_for(name, *(args << options.merge(:builder => AngularFormBuilder)), &block) diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index e2c5d4e854..c52afb060c 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -1,4 +1,4 @@ -= angular_form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path, :html => {'ng-app' => 'enterprise_fees', 'ng-controller' => 'AdminEnterpriseFeesCtrl'} do |enterprise_fee_set_form| += ng_form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path, :html => {'ng-app' => 'enterprise_fees', 'ng-controller' => 'AdminEnterpriseFeesCtrl'} do |enterprise_fee_set_form| - if @enterprise_fee_set.errors.present? %h2 Errors %ul @@ -17,14 +17,14 @@ %th Calculator values %th %tbody - = enterprise_fee_set_form.angular_fields_for :collection do |f| + = enterprise_fee_set_form.ng_fields_for :collection do |f| %tr{'ng-repeat' => 'enterprise_fee in enterprise_fees | filter:query'} %td - = f.angular_hidden_field :id - = f.angular_collection_select :enterprise_id, Enterprise.all, :id, :name, 'enterprise_fee.enterprise_id', :include_blank => true - %td= f.angular_select :fee_type, enterprise_fee_type_options, 'enterprise_fee.fee_type' - %td= f.angular_text_field :name - %td= f.angular_collection_select :calculator_type, @calculators, :name, :description, 'enterprise_fee.calculator_type', {'class' => 'calculator_type', 'ng-model' => 'calculatorType', 'spree-ensure-calculator-preferences-match-type' => "1"} + = f.ng_hidden_field :id + = f.ng_collection_select :enterprise_id, Enterprise.all, :id, :name, 'enterprise_fee.enterprise_id', :include_blank => true + %td= f.ng_select :fee_type, enterprise_fee_type_options, 'enterprise_fee.fee_type' + %td= f.ng_text_field :name + %td= f.ng_collection_select :calculator_type, @calculators, :name, :description, 'enterprise_fee.calculator_type', {'class' => 'calculator_type', 'ng-model' => 'calculatorType', 'spree-ensure-calculator-preferences-match-type' => "1"} %td{'ng-bind-html-unsafe-compiled' => 'enterprise_fee.calculator_settings'} %td{'spree-delete-resource' => "1"}