solidus_backend 1.2.3 → 1.3.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +3 -3
- data/Rakefile +1 -1
- data/app/assets/javascripts/spree/backend.js +5 -4
- data/app/assets/javascripts/spree/backend/adjustments.js.coffee +1 -1
- data/app/assets/javascripts/spree/backend/{admin.js.erb → admin.js} +13 -44
- data/app/assets/javascripts/spree/backend/checkouts/edit.js +36 -25
- data/app/assets/javascripts/spree/backend/components/tabs.coffee +79 -0
- data/app/assets/javascripts/spree/backend/components/tooltips.js +31 -0
- data/app/assets/javascripts/spree/backend/images/index.js.coffee +0 -3
- data/app/assets/javascripts/spree/backend/{line_items_on_order_edit.js.erb → line_items_on_order_edit.js} +3 -10
- data/app/assets/javascripts/spree/backend/{option_type_autocomplete.js.erb → option_type_autocomplete.js} +0 -0
- data/app/assets/javascripts/spree/backend/payments/edit.js.coffee +32 -147
- data/app/assets/javascripts/spree/backend/routes.js +1 -0
- data/app/assets/javascripts/spree/backend/shipments.js +314 -0
- data/app/assets/javascripts/spree/backend/{spree-select2.js.erb → spree-select2.js} +2 -11
- data/app/assets/javascripts/spree/backend/stock_management/index_add_forms.coffee +52 -73
- data/app/assets/javascripts/spree/backend/stock_management/index_update_forms.coffee +54 -52
- data/app/assets/javascripts/spree/backend/stock_management/stock_item.coffee +4 -37
- data/app/assets/javascripts/spree/backend/stock_movement.js.coffee +1 -1
- data/app/assets/javascripts/spree/backend/stock_transfers/edit.coffee +0 -10
- data/app/assets/javascripts/spree/backend/stock_transfers/receive.coffee +0 -10
- data/app/assets/javascripts/spree/backend/{taxon_autocomplete.js.erb → taxon_autocomplete.js} +10 -6
- data/app/assets/javascripts/spree/backend/templates/stock_items/stock_location_stock_item.hbs +25 -19
- data/app/assets/javascripts/spree/backend/user_picker.js +8 -5
- data/app/assets/javascripts/spree/backend/{variant_autocomplete.js.coffee.erb → variant_autocomplete.js.coffee} +2 -4
- data/app/assets/stylesheets/spree/backend.css +0 -3
- data/app/assets/stylesheets/spree/backend/_bootstrap_custom.scss +666 -0
- data/app/assets/stylesheets/spree/backend/components/_navigation.scss +17 -0
- data/app/assets/stylesheets/spree/backend/components/_progress.scss +1 -0
- data/app/assets/stylesheets/spree/backend/components/_tabs.scss +108 -0
- data/app/assets/stylesheets/spree/backend/plugins/_bootstrap_tooltip.scss +47 -0
- data/app/assets/stylesheets/spree/backend/sections/_orders.scss +1 -1
- data/app/assets/stylesheets/spree/backend/sections/_payments.scss +14 -0
- data/app/assets/stylesheets/spree/backend/sections/_stock_management.scss +11 -9
- data/app/assets/stylesheets/spree/backend/sections/_stock_transfers.scss +0 -49
- data/app/assets/stylesheets/spree/backend/shared/_forms.scss +11 -14
- data/app/assets/stylesheets/spree/backend/shared/_header.scss +30 -0
- data/app/assets/stylesheets/spree/backend/shared/_layout.scss +0 -105
- data/app/assets/stylesheets/spree/backend/shared/_skeleton.scss +242 -0
- data/app/assets/stylesheets/spree/backend/shared/_tables.scss +2 -0
- data/app/assets/stylesheets/spree/backend/shared/_typography.scss +5 -1
- data/app/assets/stylesheets/spree/backend/shared/_utilities.scss +35 -0
- data/app/assets/stylesheets/spree/backend/spree_admin.scss +9 -5
- data/app/controllers/spree/admin/adjustments_controller.rb +2 -3
- data/app/controllers/spree/admin/base_controller.rb +46 -56
- data/app/controllers/spree/admin/cancellations_controller.rb +1 -1
- data/app/controllers/spree/admin/countries_controller.rb +0 -2
- data/app/controllers/spree/admin/customer_returns_controller.rb +1 -1
- data/app/controllers/spree/admin/general_settings_controller.rb +6 -8
- data/app/controllers/spree/admin/images_controller.rb +16 -17
- data/app/controllers/spree/admin/log_entries_controller.rb +1 -2
- data/app/controllers/spree/admin/option_types_controller.rb +17 -21
- data/app/controllers/spree/admin/option_values_controller.rb +1 -1
- data/app/controllers/spree/admin/orders/customer_details_controller.rb +21 -22
- data/app/controllers/spree/admin/orders_controller.rb +41 -42
- data/app/controllers/spree/admin/payment_methods_controller.rb +18 -18
- data/app/controllers/spree/admin/payments_controller.rb +11 -11
- data/app/controllers/spree/admin/product_properties_controller.rb +13 -13
- data/app/controllers/spree/admin/products_controller.rb +4 -4
- data/app/controllers/spree/admin/promotion_actions_controller.rb +15 -11
- data/app/controllers/spree/admin/promotion_codes_controller.rb +0 -1
- data/app/controllers/spree/admin/promotion_rules_controller.rb +15 -15
- data/app/controllers/spree/admin/promotions_controller.rb +35 -35
- data/app/controllers/spree/admin/prototypes_controller.rb +2 -3
- data/app/controllers/spree/admin/reports_controller.rb +11 -5
- data/app/controllers/spree/admin/resource_controller.rb +130 -133
- data/app/controllers/spree/admin/return_authorizations_controller.rb +2 -2
- data/app/controllers/spree/admin/return_items_controller.rb +0 -1
- data/app/controllers/spree/admin/search_controller.rb +1 -3
- data/app/controllers/spree/admin/shipping_methods_controller.rb +3 -3
- data/app/controllers/spree/admin/states_controller.rb +10 -10
- data/app/controllers/spree/admin/stock_items_controller.rb +42 -42
- data/app/controllers/spree/admin/stock_locations_controller.rb +4 -12
- data/app/controllers/spree/admin/stock_movements_controller.rb +1 -1
- data/app/controllers/spree/admin/stock_transfers_controller.rb +7 -4
- data/app/controllers/spree/admin/store_credits_controller.rb +4 -4
- data/app/controllers/spree/admin/style_guide_controller.rb +3 -0
- data/app/controllers/spree/admin/taxonomies_controller.rb +0 -6
- data/app/controllers/spree/admin/taxons_controller.rb +13 -36
- data/app/controllers/spree/admin/users_controller.rb +54 -72
- data/app/controllers/spree/admin/variants_controller.rb +22 -35
- data/app/controllers/spree/admin/variants_including_master_controller.rb +0 -2
- data/app/controllers/spree/admin/zones_controller.rb +11 -11
- data/app/helpers/spree/admin/adjustments_helper.rb +4 -5
- data/app/helpers/spree/admin/base_helper.rb +32 -34
- data/app/helpers/spree/admin/general_settings_helper.rb +1 -1
- data/app/helpers/spree/admin/images_helper.rb +0 -1
- data/app/helpers/spree/admin/navigation_helper.rb +41 -35
- data/app/helpers/spree/admin/orders_helper.rb +5 -6
- data/app/helpers/spree/admin/payments_helper.rb +1 -1
- data/app/helpers/spree/admin/products_helper.rb +7 -7
- data/app/helpers/spree/admin/stock_locations_helper.rb +1 -1
- data/app/helpers/spree/admin/store_credit_events_helper.rb +6 -6
- data/app/helpers/spree/admin/tables_helper.rb +0 -2
- data/app/helpers/spree/admin/taxons_helper.rb +1 -1
- data/app/helpers/spree/promotion_rules_helper.rb +1 -4
- data/app/models/spree/backend_configuration.rb +1 -0
- data/app/views/kaminari/solidus_admin/_first_page.html.erb +3 -0
- data/app/views/kaminari/solidus_admin/_gap.html.erb +3 -0
- data/app/views/kaminari/solidus_admin/_last_page.html.erb +3 -0
- data/app/views/kaminari/solidus_admin/_next_page.html.erb +3 -0
- data/app/views/kaminari/solidus_admin/_page.html.erb +9 -0
- data/app/views/kaminari/solidus_admin/_paginator.html.erb +15 -0
- data/app/views/kaminari/solidus_admin/_prev_page.html.erb +3 -0
- data/app/views/spree/admin/adjustment_reasons/edit.html.erb +2 -0
- data/app/views/spree/admin/adjustment_reasons/index.html.erb +7 -6
- data/app/views/spree/admin/adjustment_reasons/new.html.erb +2 -0
- data/app/views/spree/admin/adjustment_reasons/shared/_form.html.erb +3 -3
- data/app/views/spree/admin/adjustments/_adjustments_table.html.erb +4 -4
- data/app/views/spree/admin/adjustments/_form.html.erb +5 -5
- data/app/views/spree/admin/adjustments/edit.html.erb +1 -2
- data/app/views/spree/admin/adjustments/new.html.erb +0 -1
- data/app/views/spree/admin/cancellations/index.html.erb +6 -6
- data/app/views/spree/admin/countries/_form.html.erb +3 -3
- data/app/views/spree/admin/countries/edit.html.erb +1 -1
- data/app/views/spree/admin/countries/index.html.erb +4 -4
- data/app/views/spree/admin/countries/new.html.erb +1 -1
- data/app/views/spree/admin/customer_returns/_reimbursements_table.html.erb +4 -4
- data/app/views/spree/admin/customer_returns/_return_item_decision.html.erb +10 -10
- data/app/views/spree/admin/customer_returns/_return_item_selection.html.erb +11 -11
- data/app/views/spree/admin/customer_returns/edit.html.erb +2 -2
- data/app/views/spree/admin/customer_returns/index.html.erb +9 -10
- data/app/views/spree/admin/customer_returns/new.html.erb +2 -3
- data/app/views/spree/admin/general_settings/edit.html.erb +37 -26
- data/app/views/spree/admin/images/_form.html.erb +2 -2
- data/app/views/spree/admin/images/edit.html.erb +0 -1
- data/app/views/spree/admin/images/index.html.erb +2 -2
- data/app/views/spree/admin/images/new.html.erb +0 -3
- data/app/views/spree/admin/log_entries/index.html.erb +2 -2
- data/app/views/spree/admin/option_types/_form.html.erb +3 -3
- data/app/views/spree/admin/option_types/_option_value_fields.html.erb +1 -1
- data/app/views/spree/admin/option_types/edit.html.erb +7 -5
- data/app/views/spree/admin/option_types/index.html.erb +6 -7
- data/app/views/spree/admin/orders/_add_product.html.erb +1 -1
- data/app/views/spree/admin/orders/_adjustments.html.erb +4 -2
- data/app/views/spree/admin/orders/_carton.html.erb +7 -7
- data/app/views/spree/admin/orders/_carton_manifest.html.erb +3 -5
- data/app/views/spree/admin/orders/_form.html.erb +0 -1
- data/app/views/spree/admin/orders/_line_items.html.erb +10 -8
- data/app/views/spree/admin/orders/_line_items_edit_form.html.erb +0 -2
- data/app/views/spree/admin/orders/_risk_analysis.html.erb +3 -1
- data/app/views/spree/admin/orders/_shipment.html.erb +99 -100
- data/app/views/spree/admin/orders/_shipment_manifest.html.erb +4 -9
- data/app/views/spree/admin/orders/confirm/_line_items.html.erb +4 -4
- data/app/views/spree/admin/orders/confirm/_payments.html.erb +7 -7
- data/app/views/spree/admin/orders/confirm/_shipment.html.erb +10 -7
- data/app/views/spree/admin/orders/confirm/_shipment_manifest.html.erb +2 -2
- data/app/views/spree/admin/orders/customer_details/_form.html.erb +1 -5
- data/app/views/spree/admin/orders/index.html.erb +15 -14
- data/app/views/spree/admin/payment_methods/_form.html.erb +9 -10
- data/app/views/spree/admin/payment_methods/edit.html.erb +1 -1
- data/app/views/spree/admin/payment_methods/index.html.erb +18 -17
- data/app/views/spree/admin/payment_methods/new.html.erb +2 -2
- data/app/views/spree/admin/payments/_capture_events.html.erb +2 -2
- data/app/views/spree/admin/payments/_form.html.erb +3 -3
- data/app/views/spree/admin/payments/_list.html.erb +29 -17
- data/app/views/spree/admin/payments/credit.html.erb +3 -3
- data/app/views/spree/admin/payments/index.html.erb +3 -3
- data/app/views/spree/admin/payments/new.html.erb +2 -2
- data/app/views/spree/admin/payments/show.html.erb +3 -3
- data/app/views/spree/admin/payments/source_forms/_gateway.html.erb +4 -4
- data/app/views/spree/admin/payments/source_views/_gateway.html.erb +4 -4
- data/app/views/spree/admin/payments/source_views/_storecredit.html.erb +5 -5
- data/app/views/spree/admin/product_properties/index.html.erb +6 -8
- data/app/views/spree/admin/products/_form.html.erb +33 -33
- data/app/views/spree/admin/products/_properties_form.erb +1 -1
- data/app/views/spree/admin/products/index.html.erb +10 -9
- data/app/views/spree/admin/products/new.html.erb +6 -5
- data/app/views/spree/admin/promotion_codes/index.csv.ruby +2 -2
- data/app/views/spree/admin/promotions/_actions.html.erb +2 -2
- data/app/views/spree/admin/promotions/_form.html.erb +2 -2
- data/app/views/spree/admin/promotions/_rules.html.erb +1 -1
- data/app/views/spree/admin/promotions/actions/_promotion_calculators_with_custom_fields.html.erb +1 -1
- data/app/views/spree/admin/promotions/calculators/tiered_flat_rate/_fields.html.erb +1 -1
- data/app/views/spree/admin/promotions/calculators/tiered_percent/_fields.html.erb +1 -1
- data/app/views/spree/admin/promotions/index.html.erb +15 -16
- data/app/views/spree/admin/promotions/rules/_item_total.html.erb +5 -2
- data/app/views/spree/admin/promotions/rules/_option_value.html.erb +3 -3
- data/app/views/spree/admin/properties/_form.html.erb +2 -2
- data/app/views/spree/admin/properties/index.html.erb +8 -9
- data/app/views/spree/admin/prototypes/_form.html.erb +4 -4
- data/app/views/spree/admin/prototypes/_prototypes.html.erb +2 -2
- data/app/views/spree/admin/prototypes/index.html.erb +7 -10
- data/app/views/spree/admin/prototypes/new.js.erb +1 -1
- data/app/views/spree/admin/prototypes/show.html.erb +1 -1
- data/app/views/spree/admin/refund_reasons/edit.html.erb +2 -0
- data/app/views/spree/admin/refund_reasons/index.html.erb +7 -8
- data/app/views/spree/admin/refund_reasons/new.html.erb +2 -0
- data/app/views/spree/admin/refund_reasons/shared/_form.html.erb +2 -2
- data/app/views/spree/admin/refunds/edit.html.erb +3 -4
- data/app/views/spree/admin/refunds/new.html.erb +4 -5
- data/app/views/spree/admin/reimbursement_types/index.html.erb +4 -4
- data/app/views/spree/admin/reimbursements/edit.html.erb +10 -11
- data/app/views/spree/admin/reimbursements/index.html.erb +4 -6
- data/app/views/spree/admin/reimbursements/show.html.erb +12 -12
- data/app/views/spree/admin/return_authorizations/_form.html.erb +15 -15
- data/app/views/spree/admin/return_authorizations/edit.html.erb +1 -2
- data/app/views/spree/admin/return_authorizations/index.html.erb +5 -5
- data/app/views/spree/admin/return_authorizations/new.html.erb +1 -2
- data/app/views/spree/admin/return_reasons/index.html.erb +2 -2
- data/app/views/spree/admin/shared/_address_form.html.erb +10 -10
- data/app/views/spree/admin/shared/_areas_tabs.html.erb +19 -0
- data/app/views/spree/admin/shared/_calculator_fields.html.erb +2 -2
- data/app/views/spree/admin/shared/_configuration_menu.html.erb +9 -64
- data/app/views/spree/admin/shared/_destroy.js.erb +0 -2
- data/app/views/spree/admin/shared/_edit_resource_links.html.erb +0 -1
- data/app/views/spree/admin/shared/_flash.html.erb +11 -0
- data/app/views/spree/admin/shared/_general_tabs.html.erb +2 -0
- data/app/views/spree/admin/shared/_head.html.erb +10 -1
- data/app/views/spree/admin/shared/_header.html.erb +13 -5
- data/app/views/spree/admin/shared/_navigation.html.erb +1 -1
- data/app/views/spree/admin/shared/_navigation_header.html.erb +5 -0
- data/app/views/spree/admin/shared/_new_resource_links.html.erb +0 -1
- data/app/views/spree/admin/shared/_no_objects_found.html.erb +4 -0
- data/app/views/spree/admin/shared/_order_submenu.html.erb +18 -18
- data/app/views/spree/admin/shared/_order_summary.html.erb +12 -12
- data/app/views/spree/admin/shared/_order_tabs.html.erb +4 -1
- data/app/views/spree/admin/shared/_payments_tabs.html.erb +9 -0
- data/app/views/spree/admin/shared/_product_sub_menu.html.erb +2 -2
- data/app/views/spree/admin/shared/_product_tabs.html.erb +9 -14
- data/app/views/spree/admin/shared/_promotion_sub_menu.html.erb +1 -1
- data/app/views/spree/admin/shared/_refunds.html.erb +6 -6
- data/app/views/spree/admin/shared/_settings_checkout_tabs.html.erb +21 -0
- data/app/views/spree/admin/shared/_settings_sub_menu.html.erb +26 -0
- data/app/views/spree/admin/shared/_shipping_tabs.html.erb +17 -0
- data/app/views/spree/admin/shared/_spinner.html.erb +6 -0
- data/app/views/spree/admin/shared/_stock_sub_menu.html.erb +3 -3
- data/app/views/spree/admin/shared/_table_filter.html.erb +1 -1
- data/app/views/spree/admin/shared/_tabs.html.erb +4 -2
- data/app/views/spree/admin/shared/_taxes_tabs.html.erb +13 -0
- data/app/views/spree/admin/shared/_variant_search.html.erb +2 -2
- data/app/views/spree/admin/shared/named_types/_edit.html.erb +2 -0
- data/app/views/spree/admin/shared/named_types/_form.html.erb +3 -5
- data/app/views/spree/admin/shared/named_types/_index.html.erb +4 -5
- data/app/views/spree/admin/shared/named_types/_new.html.erb +2 -0
- data/app/views/spree/admin/shipping_categories/_form.html.erb +2 -2
- data/app/views/spree/admin/shipping_categories/edit.html.erb +1 -1
- data/app/views/spree/admin/shipping_categories/index.html.erb +6 -7
- data/app/views/spree/admin/shipping_categories/new.html.erb +1 -1
- data/app/views/spree/admin/shipping_methods/_form.html.erb +26 -8
- data/app/views/spree/admin/shipping_methods/edit.html.erb +1 -1
- data/app/views/spree/admin/shipping_methods/index.html.erb +10 -11
- data/app/views/spree/admin/shipping_methods/new.html.erb +1 -1
- data/app/views/spree/admin/states/_form.html.erb +2 -2
- data/app/views/spree/admin/states/_state_list.html.erb +3 -3
- data/app/views/spree/admin/states/edit.html.erb +1 -1
- data/app/views/spree/admin/states/index.html.erb +3 -3
- data/app/views/spree/admin/states/new.html.erb +1 -1
- data/app/views/spree/admin/stock_items/_stock_management.html.erb +15 -27
- data/app/views/spree/admin/stock_items/index.html.erb +1 -3
- data/app/views/spree/admin/stock_locations/_form.html.erb +17 -18
- data/app/views/spree/admin/stock_locations/_transfer_stock_form.html.erb +2 -3
- data/app/views/spree/admin/stock_locations/edit.html.erb +1 -1
- data/app/views/spree/admin/stock_locations/index.html.erb +9 -10
- data/app/views/spree/admin/stock_locations/new.html.erb +1 -1
- data/app/views/spree/admin/stock_movements/_form.html.erb +2 -2
- data/app/views/spree/admin/stock_movements/index.html.erb +8 -9
- data/app/views/spree/admin/stock_transfers/_stock_movements.html.erb +4 -4
- data/app/views/spree/admin/stock_transfers/_transfer_item_actions.html.erb +1 -1
- data/app/views/spree/admin/stock_transfers/_transfer_item_table.html.erb +1 -1
- data/app/views/spree/admin/stock_transfers/edit.html.erb +8 -21
- data/app/views/spree/admin/stock_transfers/index.html.erb +8 -6
- data/app/views/spree/admin/stock_transfers/new.html.erb +2 -3
- data/app/views/spree/admin/stock_transfers/receive.html.erb +6 -21
- data/app/views/spree/admin/stock_transfers/show.html.erb +8 -8
- data/app/views/spree/admin/stock_transfers/tracking_info.html.erb +10 -23
- data/app/views/spree/admin/store_credits/_form.html.erb +3 -3
- data/app/views/spree/admin/store_credits/_update_reason_field.html.erb +1 -1
- data/app/views/spree/admin/store_credits/edit_amount.html.erb +1 -1
- data/app/views/spree/admin/store_credits/edit_validity.html.erb +0 -1
- data/app/views/spree/admin/store_credits/index.html.erb +11 -12
- data/app/views/spree/admin/store_credits/show.html.erb +11 -11
- data/app/views/spree/admin/style_guide/topics/components/_tabs.html.erb +17 -0
- data/app/views/spree/admin/style_guide/topics/forms/_building_forms.html.erb +2 -2
- data/app/views/spree/admin/style_guide/topics/messaging/_tooltips.html.erb +1 -1
- data/app/views/spree/admin/tax_categories/_form.html.erb +5 -7
- data/app/views/spree/admin/tax_categories/edit.html.erb +1 -1
- data/app/views/spree/admin/tax_categories/index.html.erb +8 -9
- data/app/views/spree/admin/tax_categories/new.html.erb +1 -1
- data/app/views/spree/admin/tax_categories/show.html.erb +1 -1
- data/app/views/spree/admin/tax_rates/_form.html.erb +4 -4
- data/app/views/spree/admin/tax_rates/edit.html.erb +1 -1
- data/app/views/spree/admin/tax_rates/index.html.erb +12 -13
- data/app/views/spree/admin/tax_rates/new.html.erb +2 -2
- data/app/views/spree/admin/taxonomies/_form.html.erb +1 -1
- data/app/views/spree/admin/taxonomies/_list.html.erb +1 -1
- data/app/views/spree/admin/taxonomies/edit.erb +0 -3
- data/app/views/spree/admin/taxonomies/index.html.erb +4 -7
- data/app/views/spree/admin/taxonomies/new.html.erb +1 -3
- data/app/views/spree/admin/taxons/_form.html.erb +8 -7
- data/app/views/spree/admin/taxons/_taxon_table.html.erb +1 -1
- data/app/views/spree/admin/taxons/edit.html.erb +1 -4
- data/app/views/spree/admin/taxons/index.html.erb +1 -1
- data/app/views/spree/admin/trackers/_form.html.erb +3 -3
- data/app/views/spree/admin/trackers/edit.html.erb +1 -1
- data/app/views/spree/admin/trackers/index.html.erb +7 -8
- data/app/views/spree/admin/trackers/new.html.erb +1 -1
- data/app/views/spree/admin/users/_addresses_form.html.erb +2 -4
- data/app/views/spree/admin/users/_form.html.erb +6 -6
- data/app/views/spree/admin/users/_sidebar.html.erb +5 -5
- data/app/views/spree/admin/users/addresses.html.erb +1 -1
- data/app/views/spree/admin/users/edit.html.erb +0 -2
- data/app/views/spree/admin/users/index.html.erb +2 -2
- data/app/views/spree/admin/users/items.html.erb +11 -12
- data/app/views/spree/admin/users/orders.html.erb +9 -10
- data/app/views/spree/admin/variants/_form.html.erb +60 -34
- data/app/views/spree/admin/variants/_table.html.erb +47 -0
- data/app/views/spree/admin/variants/_table_filter.html.erb +25 -0
- data/app/views/spree/admin/variants/edit.html.erb +6 -1
- data/app/views/spree/admin/variants/index.html.erb +28 -112
- data/app/views/spree/admin/variants/new.html.erb +1 -1
- data/app/views/spree/admin/variants/new.js.erb +2 -3
- data/app/views/spree/admin/zones/_country_members.html.erb +2 -2
- data/app/views/spree/admin/zones/_form.html.erb +3 -3
- data/app/views/spree/admin/zones/_state_members.html.erb +2 -2
- data/app/views/spree/admin/zones/edit.html.erb +1 -1
- data/app/views/spree/admin/zones/index.html.erb +9 -10
- data/app/views/spree/admin/zones/new.html.erb +1 -1
- data/app/views/spree/layouts/admin.html.erb +15 -33
- data/config/initializers/assets.rb +7 -1
- data/config/initializers/form_builder.rb +2 -3
- data/config/routes.rb +12 -11
- data/lib/spree/backend.rb +1 -0
- data/lib/spree/backend/action_callbacks.rb +0 -1
- data/lib/spree/backend/callbacks.rb +3 -5
- data/lib/spree/backend/engine.rb +1 -14
- data/script/rails +0 -1
- data/solidus_backend.gemspec +1 -0
- data/spec/controllers/spree/admin/base_controller_spec.rb +2 -2
- data/spec/controllers/spree/admin/cancellations_controller_spec.rb +2 -2
- data/spec/controllers/spree/admin/customer_returns_controller_spec.rb +6 -7
- data/spec/controllers/spree/admin/general_settings_controller_spec.rb +1 -1
- data/spec/controllers/spree/admin/missing_products_controller_spec.rb +2 -4
- data/spec/controllers/spree/admin/orders/customer_details_controller_spec.rb +0 -1
- data/spec/controllers/spree/admin/orders_controller_spec.rb +27 -91
- data/spec/controllers/spree/admin/payment_methods_controller_spec.rb +38 -13
- data/spec/controllers/spree/admin/payments_controller_spec.rb +108 -104
- data/spec/controllers/spree/admin/products_controller_spec.rb +7 -7
- data/spec/controllers/spree/admin/promotion_actions_controller_spec.rb +3 -3
- data/spec/controllers/spree/admin/promotion_codes_controller_spec.rb +1 -1
- data/spec/controllers/spree/admin/promotion_rules_controller_spec.rb +3 -3
- data/spec/controllers/spree/admin/promotions_controller_spec.rb +6 -9
- data/spec/controllers/spree/admin/refunds_controller_spec.rb +0 -1
- data/spec/controllers/spree/admin/reimbursements_controller_spec.rb +3 -3
- data/spec/controllers/spree/admin/reports_controller_spec.rb +14 -9
- data/spec/controllers/spree/admin/resource_controller_spec.rb +6 -6
- data/spec/controllers/spree/admin/return_authorizations_controller_spec.rb +13 -13
- data/spec/controllers/spree/admin/return_items_controller_spec.rb +2 -2
- data/spec/controllers/spree/admin/search_controller_spec.rb +2 -2
- data/spec/controllers/spree/admin/shipping_methods_controller_spec.rb +3 -3
- data/spec/controllers/spree/admin/stock_items_controller_spec.rb +1 -1
- data/spec/controllers/spree/admin/stock_locations_controller_spec.rb +8 -7
- data/spec/controllers/spree/admin/stock_transfers_controller_spec.rb +13 -25
- data/spec/controllers/spree/admin/store_credits_controller_spec.rb +3 -4
- data/spec/controllers/spree/admin/users_controller_spec.rb +19 -19
- data/spec/controllers/spree/admin/variants_controller_spec.rb +7 -6
- data/spec/features/admin/configuration/analytics_tracker_spec.rb +3 -3
- data/spec/features/admin/configuration/countries_spec.rb +1 -1
- data/spec/features/admin/configuration/general_settings_spec.rb +12 -2
- data/spec/features/admin/configuration/payment_methods_spec.rb +13 -12
- data/spec/features/admin/configuration/shipping_methods_spec.rb +7 -7
- data/spec/features/admin/configuration/states_spec.rb +12 -12
- data/spec/features/admin/configuration/stock_locations_spec.rb +5 -4
- data/spec/features/admin/configuration/tax_categories_spec.rb +6 -6
- data/spec/features/admin/configuration/tax_rates_spec.rb +4 -4
- data/spec/features/admin/configuration/taxonomies_spec.rb +6 -6
- data/spec/features/admin/configuration/zones_spec.rb +6 -6
- data/spec/features/admin/homepage_spec.rb +2 -5
- data/spec/features/admin/locale_spec.rb +9 -7
- data/spec/features/admin/orders/adjustments_promotions_spec.rb +15 -15
- data/spec/features/admin/orders/adjustments_spec.rb +17 -18
- data/spec/features/admin/orders/cancelling_and_resuming_spec.rb +1 -2
- data/spec/features/admin/orders/cancelling_inventory_spec.rb +1 -1
- data/spec/features/admin/orders/customer_details_spec.rb +12 -12
- data/spec/features/admin/orders/line_items_spec.rb +3 -3
- data/spec/features/admin/orders/listing_spec.rb +21 -1
- data/spec/features/admin/orders/log_entries_spec.rb +7 -7
- data/spec/features/admin/orders/new_order_spec.rb +7 -7
- data/spec/features/admin/orders/order_details_spec.rb +41 -29
- data/spec/features/admin/orders/payments_spec.rb +20 -28
- data/spec/features/admin/orders/risk_analysis_spec.rb +2 -2
- data/spec/features/admin/orders/shipments_spec.rb +11 -6
- data/spec/features/admin/products/edit/images_spec.rb +14 -14
- data/spec/features/admin/products/edit/products_spec.rb +10 -10
- data/spec/features/admin/products/edit/taxons_spec.rb +2 -2
- data/spec/features/admin/products/edit/variants_spec.rb +9 -9
- data/spec/features/admin/products/option_types_spec.rb +11 -12
- data/spec/features/admin/products/products_spec.rb +66 -68
- data/spec/features/admin/products/properties_spec.rb +14 -14
- data/spec/features/admin/products/prototypes_spec.rb +3 -3
- data/spec/features/admin/products/stock_management_spec.rb +16 -11
- data/spec/features/admin/products/variant_spec.rb +4 -4
- data/spec/features/admin/promotion_adjustments_spec.rb +41 -41
- data/spec/features/admin/reports_spec.rb +9 -9
- data/spec/features/admin/stock_transfer_spec.rb +8 -22
- data/spec/features/admin/store_credits_spec.rb +0 -1
- data/spec/features/admin/taxons_spec.rb +5 -6
- data/spec/features/admin/users_spec.rb +6 -9
- data/spec/helpers/admin/base_helper_spec.rb +1 -2
- data/spec/helpers/admin/navigation_helper_spec.rb +15 -18
- data/spec/helpers/admin/reimbursements_helper_spec.rb +3 -3
- data/spec/helpers/admin/stock_movements_helper_spec.rb +1 -3
- data/spec/helpers/admin/store_credit_events_helper_spec.rb +3 -3
- data/spec/helpers/promotion_rules_helper_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -4
- data/spec/support/feature/base_feature_helper.rb +3 -3
- data/vendor/assets/javascripts/{backbone.js → solidus_admin/backbone.js} +764 -452
- data/vendor/assets/javascripts/solidus_admin/bind-polyfill.js +28 -0
- data/vendor/assets/javascripts/solidus_admin/bootstrap.js +3560 -0
- data/vendor/assets/javascripts/solidus_admin/tether.js +1726 -0
- data/vendor/assets/javascripts/solidus_admin/underscore.js +1548 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_alert.scss +65 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_animation.scss +27 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_breadcrumb.scss +23 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_button-group.scss +224 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_buttons.scss +173 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_card.scss +292 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_carousel.scss +252 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_close.scss +28 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_code.scss +58 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_custom-forms.scss +226 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_dropdown.scss +193 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_forms.scss +452 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_grid.scss +76 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_images.scss +53 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_input-group.scss +189 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_jumbotron.scss +20 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_labels.scss +77 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_list-group.scss +140 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_media.scss +90 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_mixins.scss +55 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_modal.scss +146 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_nav.scss +162 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_navbar.scss +230 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_normalize.scss +428 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_pager.scss +57 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_pagination.scss +73 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_popover.scss +140 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_print.scss +88 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_progress.scss +156 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_reboot.scss +347 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_responsive-embed.scss +39 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_tables.scss +193 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_tooltip.scss +85 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_type.scss +157 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_utilities-background.scss +24 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_utilities-responsive.scss +49 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_utilities-spacing.scss +39 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_utilities.scss +95 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/_variables.scss +666 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/bootstrap-flex.scss +8 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/bootstrap-grid.scss +62 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/bootstrap-reboot.scss +10 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/bootstrap.scss +56 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_alert.scss +14 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_background-variant.scss +13 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_border-radius.scss +35 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_breakpoints.scss +86 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_buttons.scss +100 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_cards.scss +38 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_center-block.scss +7 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_clearfix.scss +7 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_forms.scss +89 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_gradients.scss +43 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_grid-framework.scss +44 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_grid.scss +75 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_hover.scss +59 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_image.scss +34 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_label.scss +11 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_list-group.scss +30 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_lists.scss +7 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_nav-divider.scss +10 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_navbar-align.scss +9 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_pagination.scss +22 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_progress.scss +18 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_pulls.scss +6 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_reset-filter.scss +8 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_reset-text.scss +18 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_resize.scss +6 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_screen-reader.scss +32 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_size.scss +6 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_tab-focus.scss +9 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_table-row.scss +30 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_text-emphasis.scss +12 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_text-hide.scss +8 -0
- data/vendor/assets/stylesheets/solidus_admin/bootstrap/mixins/_text-truncate.scss +8 -0
- metadata +140 -35
- data/app/assets/javascripts/spree/backend/orders/edit_form.js +0 -20
- data/app/assets/javascripts/spree/backend/shipments.js.erb +0 -353
- data/app/assets/javascripts/spree/backend/stock_management/index.coffee +0 -4
- data/app/assets/javascripts/spree/backend/stock_transfers/ship.js.coffee +0 -10
- data/app/assets/javascripts/spree/backend/taxon_tree_menu.js.coffee +0 -21
- data/app/assets/javascripts/spree/frontend/backend.js +0 -1
- data/app/assets/stylesheets/spree/backend/components/_pagination.scss +0 -17
- data/app/assets/stylesheets/spree/backend/plugins/_powertip.scss +0 -87
- data/app/assets/stylesheets/spree/backend/plugins/_token-input.scss +0 -110
- data/app/assets/stylesheets/spree/frontend/backend.css +0 -1
- data/app/views/spree/admin/orders/_line_item.html.erb +0 -9
- data/app/views/spree/admin/shared/_content_header.html.erb +0 -23
- data/app/views/spree/admin/shared/_show_resource_links.html.erb +0 -5
- data/app/views/spree/admin/shared/_update_order_state.js +0 -7
- data/vendor/assets/javascripts/jquery.cookie.js +0 -41
- data/vendor/assets/javascripts/jquery.powertip.js +0 -1165
- data/vendor/assets/javascripts/underscore-min.js +0 -1227
- data/vendor/assets/stylesheets/bootstrap/components/_media.scss +0 -61
- data/vendor/assets/stylesheets/jquery.powertip.css +0 -85
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
describe Spree::Admin::BaseHelper, :
|
|
3
|
+
describe Spree::Admin::BaseHelper, type: :helper do
|
|
4
4
|
include Spree::Admin::BaseHelper
|
|
5
5
|
|
|
6
6
|
context "#datepicker_field_value" do
|
|
@@ -14,5 +14,4 @@ describe Spree::Admin::BaseHelper, :type => :helper do
|
|
|
14
14
|
expect(datepicker_field_value(date)).to eq("2013/08/14")
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
|
-
|
|
18
17
|
end
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# coding: UTF-8
|
|
2
2
|
require 'spec_helper'
|
|
3
3
|
|
|
4
|
-
describe Spree::Admin::NavigationHelper, :
|
|
5
|
-
|
|
4
|
+
describe Spree::Admin::NavigationHelper, type: :helper do
|
|
6
5
|
describe "#tab" do
|
|
7
6
|
context "creating an admin tab" do
|
|
8
7
|
it "should capitalize the first letter of each word in the tab's label" do
|
|
@@ -11,17 +10,6 @@ describe Spree::Admin::NavigationHelper, :type => :helper do
|
|
|
11
10
|
end
|
|
12
11
|
end
|
|
13
12
|
|
|
14
|
-
it "should accept options with label and capitalize each word of it" do
|
|
15
|
-
admin_tab = helper.tab(:orders, :label => "delivered orders")
|
|
16
|
-
expect(admin_tab).to include("Delivered Orders")
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
it "should capitalize words with unicode characters" do
|
|
20
|
-
# overview
|
|
21
|
-
admin_tab = helper.tab(:orders, :label => "přehled")
|
|
22
|
-
expect(admin_tab).to include("Přehled")
|
|
23
|
-
end
|
|
24
|
-
|
|
25
13
|
describe "selection" do
|
|
26
14
|
context "when match_path option is not supplied" do
|
|
27
15
|
subject(:tab) { helper.tab(:orders) }
|
|
@@ -36,35 +24,44 @@ describe Spree::Admin::NavigationHelper, :type => :helper do
|
|
|
36
24
|
expect(subject).not_to include('class="selected"')
|
|
37
25
|
end
|
|
38
26
|
|
|
27
|
+
it "should be selected if the current path" do
|
|
28
|
+
allow(helper).to receive(:request).and_return(double(ActionDispatch::Request, fullpath: "/admin/orders"))
|
|
29
|
+
expect(subject).to include('class="selected"')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "should not be selected if not current path" do
|
|
33
|
+
allow(helper).to receive(:request).and_return(double(ActionDispatch::Request, fullpath: "/admin/products"))
|
|
34
|
+
expect(subject).not_to include('class="selected"')
|
|
35
|
+
end
|
|
39
36
|
end
|
|
40
37
|
|
|
41
38
|
context "when match_path option is supplied" do
|
|
42
39
|
before do
|
|
43
40
|
allow(helper).to receive(:admin_path).and_return("/somepath")
|
|
44
|
-
allow(helper).to receive(:request).and_return(double(ActionDispatch::Request, :
|
|
41
|
+
allow(helper).to receive(:request).and_return(double(ActionDispatch::Request, fullpath: "/somepath/orders/edit/1"))
|
|
45
42
|
end
|
|
46
43
|
|
|
47
44
|
it "should be selected if the fullpath matches" do
|
|
48
45
|
allow(controller).to receive(:controller_name).and_return("bonobos")
|
|
49
|
-
tab = helper.tab(:orders, :
|
|
46
|
+
tab = helper.tab(:orders, match_path: '/orders')
|
|
50
47
|
expect(tab).to include('class="selected"')
|
|
51
48
|
end
|
|
52
49
|
|
|
53
50
|
it "should be selected if the fullpath matches a regular expression" do
|
|
54
51
|
allow(controller).to receive(:controller_name).and_return("bonobos")
|
|
55
|
-
tab = helper.tab(:orders, :
|
|
52
|
+
tab = helper.tab(:orders, match_path: /orders$|orders\//)
|
|
56
53
|
expect(tab).to include('class="selected"')
|
|
57
54
|
end
|
|
58
55
|
|
|
59
56
|
it "should not be selected if the fullpath does not match" do
|
|
60
57
|
allow(controller).to receive(:controller_name).and_return("bonobos")
|
|
61
|
-
tab = helper.tab(:orders, :
|
|
58
|
+
tab = helper.tab(:orders, match_path: '/shady')
|
|
62
59
|
expect(tab).not_to include('class="selected"')
|
|
63
60
|
end
|
|
64
61
|
|
|
65
62
|
it "should not be selected if the fullpath does not match a regular expression" do
|
|
66
63
|
allow(controller).to receive(:controller_name).and_return("bonobos")
|
|
67
|
-
tab = helper.tab(:orders, :
|
|
64
|
+
tab = helper.tab(:orders, match_path: /shady$|shady\//)
|
|
68
65
|
expect(tab).not_to include('class="selected"')
|
|
69
66
|
end
|
|
70
67
|
end
|
|
@@ -10,17 +10,17 @@ describe Spree::Admin::ReimbursementsHelper, type: :helper do
|
|
|
10
10
|
|
|
11
11
|
context 'when status is reimbursed' do
|
|
12
12
|
let(:status) { 'reimbursed' }
|
|
13
|
-
it { is_expected.to eq 'success'}
|
|
13
|
+
it { is_expected.to eq 'success' }
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
context 'when status is pending' do
|
|
17
17
|
let(:status) { 'pending' }
|
|
18
|
-
it { is_expected.to eq 'notice'}
|
|
18
|
+
it { is_expected.to eq 'notice' }
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
context 'when status is pending' do
|
|
22
22
|
let(:status) { 'errored' }
|
|
23
|
-
it { is_expected.to eq 'error'}
|
|
23
|
+
it { is_expected.to eq 'error' }
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
context 'when status is not valid' do
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
describe Spree::Admin::StockMovementsHelper, :
|
|
4
|
-
|
|
3
|
+
describe Spree::Admin::StockMovementsHelper, type: :helper do
|
|
5
4
|
describe "#pretty_originator" do
|
|
6
5
|
let!(:stock_location) { create(:stock_location_with_items) }
|
|
7
6
|
let!(:stock_item) { stock_location.stock_items.first }
|
|
@@ -25,5 +24,4 @@ describe Spree::Admin::StockMovementsHelper, :type => :helper do
|
|
|
25
24
|
end
|
|
26
25
|
end
|
|
27
26
|
end
|
|
28
|
-
|
|
29
27
|
end
|
|
@@ -64,7 +64,7 @@ describe Spree::Admin::StoreCreditEventsHelper, type: :helper do
|
|
|
64
64
|
let(:originator) { create(:user) }
|
|
65
65
|
|
|
66
66
|
it "returns a link to the user's edit page" do
|
|
67
|
-
expect(subject).to eq %
|
|
67
|
+
expect(subject).to eq %(<a target=\"_blank\" href=\"/admin/users/#{originator.id}/edit\">User - #{originator.email}</a>)
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -72,7 +72,7 @@ describe Spree::Admin::StoreCreditEventsHelper, type: :helper do
|
|
|
72
72
|
let(:originator) { create(:payment) }
|
|
73
73
|
|
|
74
74
|
it "returns a link to the order's payments page" do
|
|
75
|
-
expect(subject).to eq %
|
|
75
|
+
expect(subject).to eq %(<a target=\"_blank\" href=\"/admin/orders/#{originator.order.number}/payments/#{originator.id}\">Payment - Order ##{originator.order.number}</a>)
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
|
|
@@ -80,7 +80,7 @@ describe Spree::Admin::StoreCreditEventsHelper, type: :helper do
|
|
|
80
80
|
let(:originator) { create(:refund, amount: 1.0) }
|
|
81
81
|
|
|
82
82
|
it "returns a link to the order's payments page" do
|
|
83
|
-
expect(subject).to eq %
|
|
83
|
+
expect(subject).to eq %(<a target=\"_blank\" href=\"/admin/orders/#{originator.payment.order.number}/payments\">Refund - Order ##{originator.payment.order.number}</a>)
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
module Spree
|
|
3
|
-
describe Spree::PromotionRulesHelper, :
|
|
3
|
+
describe Spree::PromotionRulesHelper, type: :helper do
|
|
4
4
|
it "does not include existing rules in options" do
|
|
5
5
|
promotion = Spree::Promotion.new
|
|
6
6
|
promotion.promotion_rules << Spree::Promotion::Rules::ItemTotal.new
|
data/spec/spec_helper.rb
CHANGED
|
@@ -26,7 +26,7 @@ require 'rspec/rails'
|
|
|
26
26
|
|
|
27
27
|
# Requires supporting files with custom matchers and macros, etc,
|
|
28
28
|
# in ./support/ and its subdirectories.
|
|
29
|
-
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
|
29
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
|
30
30
|
|
|
31
31
|
require 'database_cleaner'
|
|
32
32
|
require 'ffaker'
|
|
@@ -48,6 +48,10 @@ Capybara.save_and_open_page_path = ENV['CIRCLE_ARTIFACTS'] if ENV['CIRCLE_ARTIFA
|
|
|
48
48
|
require 'capybara/poltergeist'
|
|
49
49
|
Capybara.javascript_driver = :poltergeist
|
|
50
50
|
|
|
51
|
+
ActionView::Base.raise_on_missing_translations = true
|
|
52
|
+
|
|
53
|
+
Capybara.default_max_wait_time = ENV['DEFAULT_MAX_WAIT_TIME'].to_f if ENV['DEFAULT_MAX_WAIT_TIME'].present?
|
|
54
|
+
|
|
51
55
|
RSpec.configure do |config|
|
|
52
56
|
config.color = true
|
|
53
57
|
config.infer_spec_type_from_file_location!
|
|
@@ -70,7 +74,6 @@ RSpec.configure do |config|
|
|
|
70
74
|
config.before(:each) do
|
|
71
75
|
Rails.cache.clear
|
|
72
76
|
reset_spree_preferences
|
|
73
|
-
WebMock.disable!
|
|
74
77
|
if RSpec.current_example.metadata[:js]
|
|
75
78
|
page.driver.browser.url_blacklist = ['http://fonts.googleapis.com']
|
|
76
79
|
DatabaseCleaner.strategy = :truncation
|
|
@@ -87,9 +90,9 @@ RSpec.configure do |config|
|
|
|
87
90
|
DatabaseCleaner.clean
|
|
88
91
|
end
|
|
89
92
|
|
|
90
|
-
config.include BaseFeatureHelper, :
|
|
93
|
+
config.include BaseFeatureHelper, type: :feature
|
|
91
94
|
|
|
92
|
-
config.after(:each, :
|
|
95
|
+
config.after(:each, type: :feature) do |example|
|
|
93
96
|
missing_translations = page.body.scan(/translation missing: #{I18n.locale}\.(.*?)[\s<\"&]/)
|
|
94
97
|
if missing_translations.any?
|
|
95
98
|
puts "Found missing translations: #{missing_translations.inspect}"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
module BaseFeatureHelper
|
|
2
|
-
def click_nav(nav_text, subnav_text=nil)
|
|
3
|
-
primary_nav = find(".admin-nav-menu>ul>li>a", text:
|
|
2
|
+
def click_nav(nav_text, subnav_text = nil)
|
|
3
|
+
primary_nav = find(".admin-nav-menu>ul>li>a", text: /#{nav_text}/i)
|
|
4
4
|
if subnav_text
|
|
5
|
-
primary_nav.find('+ul>li>a', text:
|
|
5
|
+
primary_nav.find('+ul>li>a', text: /#{subnav_text}/i).click
|
|
6
6
|
else
|
|
7
7
|
primary_nav.click
|
|
8
8
|
end
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
// Backbone.js 1.
|
|
1
|
+
// Backbone.js 1.3.2
|
|
2
2
|
|
|
3
|
-
// (c) 2010-
|
|
3
|
+
// (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
4
4
|
// Backbone may be freely distributed under the MIT license.
|
|
5
5
|
// For all details and documentation:
|
|
6
6
|
// http://backbonejs.org
|
|
7
7
|
|
|
8
|
-
(function(
|
|
8
|
+
(function(factory) {
|
|
9
|
+
|
|
10
|
+
// Establish the root object, `window` (`self`) in the browser, or `global` on the server.
|
|
11
|
+
// We use `self` instead of `window` for `WebWorker` support.
|
|
12
|
+
var root = (typeof self == 'object' && self.self === self && self) ||
|
|
13
|
+
(typeof global == 'object' && global.global === global && global);
|
|
9
14
|
|
|
10
15
|
// Set up Backbone appropriately for the environment. Start with AMD.
|
|
11
16
|
if (typeof define === 'function' && define.amd) {
|
|
@@ -17,15 +22,16 @@
|
|
|
17
22
|
|
|
18
23
|
// Next for Node.js or CommonJS. jQuery may not be needed as a module.
|
|
19
24
|
} else if (typeof exports !== 'undefined') {
|
|
20
|
-
var _ = require('underscore')
|
|
21
|
-
|
|
25
|
+
var _ = require('underscore'), $;
|
|
26
|
+
try { $ = require('jquery'); } catch (e) {}
|
|
27
|
+
factory(root, exports, _, $);
|
|
22
28
|
|
|
23
29
|
// Finally, as a browser global.
|
|
24
30
|
} else {
|
|
25
31
|
root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
}(
|
|
34
|
+
})(function(root, Backbone, _, $) {
|
|
29
35
|
|
|
30
36
|
// Initial Setup
|
|
31
37
|
// -------------
|
|
@@ -34,14 +40,11 @@
|
|
|
34
40
|
// restored later on, if `noConflict` is used.
|
|
35
41
|
var previousBackbone = root.Backbone;
|
|
36
42
|
|
|
37
|
-
// Create local
|
|
38
|
-
var
|
|
39
|
-
var push = array.push;
|
|
40
|
-
var slice = array.slice;
|
|
41
|
-
var splice = array.splice;
|
|
43
|
+
// Create a local reference to a common array method we'll want to use later.
|
|
44
|
+
var slice = Array.prototype.slice;
|
|
42
45
|
|
|
43
46
|
// Current version of the library. Keep in sync with `package.json`.
|
|
44
|
-
Backbone.VERSION = '1.
|
|
47
|
+
Backbone.VERSION = '1.3.2';
|
|
45
48
|
|
|
46
49
|
// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
|
|
47
50
|
// the `$` variable.
|
|
@@ -60,17 +63,65 @@
|
|
|
60
63
|
Backbone.emulateHTTP = false;
|
|
61
64
|
|
|
62
65
|
// Turn on `emulateJSON` to support legacy servers that can't deal with direct
|
|
63
|
-
// `application/json` requests ... will encode the body as
|
|
66
|
+
// `application/json` requests ... this will encode the body as
|
|
64
67
|
// `application/x-www-form-urlencoded` instead and will send the model in a
|
|
65
68
|
// form param named `model`.
|
|
66
69
|
Backbone.emulateJSON = false;
|
|
67
70
|
|
|
71
|
+
// Proxy Backbone class methods to Underscore functions, wrapping the model's
|
|
72
|
+
// `attributes` object or collection's `models` array behind the scenes.
|
|
73
|
+
//
|
|
74
|
+
// collection.filter(function(model) { return model.get('age') > 10 });
|
|
75
|
+
// collection.each(this.addView);
|
|
76
|
+
//
|
|
77
|
+
// `Function#apply` can be slow so we use the method's arg count, if we know it.
|
|
78
|
+
var addMethod = function(length, method, attribute) {
|
|
79
|
+
switch (length) {
|
|
80
|
+
case 1: return function() {
|
|
81
|
+
return _[method](this[attribute]);
|
|
82
|
+
};
|
|
83
|
+
case 2: return function(value) {
|
|
84
|
+
return _[method](this[attribute], value);
|
|
85
|
+
};
|
|
86
|
+
case 3: return function(iteratee, context) {
|
|
87
|
+
return _[method](this[attribute], cb(iteratee, this), context);
|
|
88
|
+
};
|
|
89
|
+
case 4: return function(iteratee, defaultVal, context) {
|
|
90
|
+
return _[method](this[attribute], cb(iteratee, this), defaultVal, context);
|
|
91
|
+
};
|
|
92
|
+
default: return function() {
|
|
93
|
+
var args = slice.call(arguments);
|
|
94
|
+
args.unshift(this[attribute]);
|
|
95
|
+
return _[method].apply(_, args);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var addUnderscoreMethods = function(Class, methods, attribute) {
|
|
100
|
+
_.each(methods, function(length, method) {
|
|
101
|
+
if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
|
|
106
|
+
var cb = function(iteratee, instance) {
|
|
107
|
+
if (_.isFunction(iteratee)) return iteratee;
|
|
108
|
+
if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
|
|
109
|
+
if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
|
|
110
|
+
return iteratee;
|
|
111
|
+
};
|
|
112
|
+
var modelMatcher = function(attrs) {
|
|
113
|
+
var matcher = _.matches(attrs);
|
|
114
|
+
return function(model) {
|
|
115
|
+
return matcher(model.attributes);
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
68
119
|
// Backbone.Events
|
|
69
120
|
// ---------------
|
|
70
121
|
|
|
71
122
|
// A module that can be mixed in to *any object* in order to provide it with
|
|
72
|
-
// custom
|
|
73
|
-
//
|
|
123
|
+
// a custom event channel. You may bind a callback to an event with `on` or
|
|
124
|
+
// remove with `off`; `trigger`-ing an event fires all callbacks in
|
|
74
125
|
// succession.
|
|
75
126
|
//
|
|
76
127
|
// var object = {};
|
|
@@ -78,123 +129,234 @@
|
|
|
78
129
|
// object.on('expand', function(){ alert('expanded'); });
|
|
79
130
|
// object.trigger('expand');
|
|
80
131
|
//
|
|
81
|
-
var Events = Backbone.Events = {
|
|
82
|
-
|
|
83
|
-
// Bind an event to a `callback` function. Passing `"all"` will bind
|
|
84
|
-
// the callback to all events fired.
|
|
85
|
-
on: function(name, callback, context) {
|
|
86
|
-
if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
|
|
87
|
-
this._events || (this._events = {});
|
|
88
|
-
var events = this._events[name] || (this._events[name] = []);
|
|
89
|
-
events.push({callback: callback, context: context, ctx: context || this});
|
|
90
|
-
return this;
|
|
91
|
-
},
|
|
132
|
+
var Events = Backbone.Events = {};
|
|
92
133
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// Remove one or many callbacks. If `context` is null, removes all
|
|
107
|
-
// callbacks with that function. If `callback` is null, removes all
|
|
108
|
-
// callbacks for the event. If `name` is null, removes all bound
|
|
109
|
-
// callbacks for all events.
|
|
110
|
-
off: function(name, callback, context) {
|
|
111
|
-
var retain, ev, events, names, i, l, j, k;
|
|
112
|
-
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
|
|
113
|
-
if (!name && !callback && !context) {
|
|
114
|
-
this._events = void 0;
|
|
115
|
-
return this;
|
|
134
|
+
// Regular expression used to split event strings.
|
|
135
|
+
var eventSplitter = /\s+/;
|
|
136
|
+
|
|
137
|
+
// Iterates over the standard `event, callback` (as well as the fancy multiple
|
|
138
|
+
// space-separated events `"change blur", callback` and jQuery-style event
|
|
139
|
+
// maps `{event: callback}`).
|
|
140
|
+
var eventsApi = function(iteratee, events, name, callback, opts) {
|
|
141
|
+
var i = 0, names;
|
|
142
|
+
if (name && typeof name === 'object') {
|
|
143
|
+
// Handle event maps.
|
|
144
|
+
if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
|
|
145
|
+
for (names = _.keys(name); i < names.length ; i++) {
|
|
146
|
+
events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
|
|
116
147
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
this._events[name] = retain = [];
|
|
122
|
-
if (callback || context) {
|
|
123
|
-
for (j = 0, k = events.length; j < k; j++) {
|
|
124
|
-
ev = events[j];
|
|
125
|
-
if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
|
|
126
|
-
(context && context !== ev.context)) {
|
|
127
|
-
retain.push(ev);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (!retain.length) delete this._events[name];
|
|
132
|
-
}
|
|
148
|
+
} else if (name && eventSplitter.test(name)) {
|
|
149
|
+
// Handle space-separated event names by delegating them individually.
|
|
150
|
+
for (names = name.split(eventSplitter); i < names.length; i++) {
|
|
151
|
+
events = iteratee(events, names[i], callback, opts);
|
|
133
152
|
}
|
|
153
|
+
} else {
|
|
154
|
+
// Finally, standard events.
|
|
155
|
+
events = iteratee(events, name, callback, opts);
|
|
156
|
+
}
|
|
157
|
+
return events;
|
|
158
|
+
};
|
|
134
159
|
|
|
135
|
-
|
|
136
|
-
|
|
160
|
+
// Bind an event to a `callback` function. Passing `"all"` will bind
|
|
161
|
+
// the callback to all events fired.
|
|
162
|
+
Events.on = function(name, callback, context) {
|
|
163
|
+
return internalOn(this, name, callback, context);
|
|
164
|
+
};
|
|
137
165
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (!eventsApi(this, 'trigger', name, args)) return this;
|
|
146
|
-
var events = this._events[name];
|
|
147
|
-
var allEvents = this._events.all;
|
|
148
|
-
if (events) triggerEvents(events, args);
|
|
149
|
-
if (allEvents) triggerEvents(allEvents, arguments);
|
|
150
|
-
return this;
|
|
151
|
-
},
|
|
166
|
+
// Guard the `listening` argument from the public API.
|
|
167
|
+
var internalOn = function(obj, name, callback, context, listening) {
|
|
168
|
+
obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
|
|
169
|
+
context: context,
|
|
170
|
+
ctx: obj,
|
|
171
|
+
listening: listening
|
|
172
|
+
});
|
|
152
173
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
var listeningTo = this._listeningTo;
|
|
157
|
-
if (!listeningTo) return this;
|
|
158
|
-
var remove = !name && !callback;
|
|
159
|
-
if (!callback && typeof name === 'object') callback = this;
|
|
160
|
-
if (obj) (listeningTo = {})[obj._listenId] = obj;
|
|
161
|
-
for (var id in listeningTo) {
|
|
162
|
-
obj = listeningTo[id];
|
|
163
|
-
obj.off(name, callback, this);
|
|
164
|
-
if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
|
|
165
|
-
}
|
|
166
|
-
return this;
|
|
174
|
+
if (listening) {
|
|
175
|
+
var listeners = obj._listeners || (obj._listeners = {});
|
|
176
|
+
listeners[listening.id] = listening;
|
|
167
177
|
}
|
|
168
178
|
|
|
179
|
+
return obj;
|
|
169
180
|
};
|
|
170
181
|
|
|
171
|
-
//
|
|
172
|
-
|
|
182
|
+
// Inversion-of-control versions of `on`. Tell *this* object to listen to
|
|
183
|
+
// an event in another object... keeping track of what it's listening to
|
|
184
|
+
// for easier unbinding later.
|
|
185
|
+
Events.listenTo = function(obj, name, callback) {
|
|
186
|
+
if (!obj) return this;
|
|
187
|
+
var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
|
|
188
|
+
var listeningTo = this._listeningTo || (this._listeningTo = {});
|
|
189
|
+
var listening = listeningTo[id];
|
|
190
|
+
|
|
191
|
+
// This object is not listening to any other events on `obj` yet.
|
|
192
|
+
// Setup the necessary references to track the listening callbacks.
|
|
193
|
+
if (!listening) {
|
|
194
|
+
var thisId = this._listenId || (this._listenId = _.uniqueId('l'));
|
|
195
|
+
listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Bind callbacks on obj, and keep track of them on listening.
|
|
199
|
+
internalOn(obj, name, callback, this, listening);
|
|
200
|
+
return this;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// The reducing API that adds a callback to the `events` object.
|
|
204
|
+
var onApi = function(events, name, callback, options) {
|
|
205
|
+
if (callback) {
|
|
206
|
+
var handlers = events[name] || (events[name] = []);
|
|
207
|
+
var context = options.context, ctx = options.ctx, listening = options.listening;
|
|
208
|
+
if (listening) listening.count++;
|
|
209
|
+
|
|
210
|
+
handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening});
|
|
211
|
+
}
|
|
212
|
+
return events;
|
|
213
|
+
};
|
|
173
214
|
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
215
|
+
// Remove one or many callbacks. If `context` is null, removes all
|
|
216
|
+
// callbacks with that function. If `callback` is null, removes all
|
|
217
|
+
// callbacks for the event. If `name` is null, removes all bound
|
|
218
|
+
// callbacks for all events.
|
|
219
|
+
Events.off = function(name, callback, context) {
|
|
220
|
+
if (!this._events) return this;
|
|
221
|
+
this._events = eventsApi(offApi, this._events, name, callback, {
|
|
222
|
+
context: context,
|
|
223
|
+
listeners: this._listeners
|
|
224
|
+
});
|
|
225
|
+
return this;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// Tell this object to stop listening to either specific events ... or
|
|
229
|
+
// to every object it's currently listening to.
|
|
230
|
+
Events.stopListening = function(obj, name, callback) {
|
|
231
|
+
var listeningTo = this._listeningTo;
|
|
232
|
+
if (!listeningTo) return this;
|
|
233
|
+
|
|
234
|
+
var ids = obj ? [obj._listenId] : _.keys(listeningTo);
|
|
235
|
+
|
|
236
|
+
for (var i = 0; i < ids.length; i++) {
|
|
237
|
+
var listening = listeningTo[ids[i]];
|
|
238
|
+
|
|
239
|
+
// If listening doesn't exist, this object is not currently
|
|
240
|
+
// listening to obj. Break out early.
|
|
241
|
+
if (!listening) break;
|
|
242
|
+
|
|
243
|
+
listening.obj.off(name, callback, this);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return this;
|
|
247
|
+
};
|
|
179
248
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
249
|
+
// The reducing API that removes a callback from the `events` object.
|
|
250
|
+
var offApi = function(events, name, callback, options) {
|
|
251
|
+
if (!events) return;
|
|
252
|
+
|
|
253
|
+
var i = 0, listening;
|
|
254
|
+
var context = options.context, listeners = options.listeners;
|
|
255
|
+
|
|
256
|
+
// Delete all events listeners and "drop" events.
|
|
257
|
+
if (!name && !callback && !context) {
|
|
258
|
+
var ids = _.keys(listeners);
|
|
259
|
+
for (; i < ids.length; i++) {
|
|
260
|
+
listening = listeners[ids[i]];
|
|
261
|
+
delete listeners[listening.id];
|
|
262
|
+
delete listening.listeningTo[listening.objId];
|
|
184
263
|
}
|
|
185
|
-
return
|
|
264
|
+
return;
|
|
186
265
|
}
|
|
187
266
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
267
|
+
var names = name ? [name] : _.keys(events);
|
|
268
|
+
for (; i < names.length; i++) {
|
|
269
|
+
name = names[i];
|
|
270
|
+
var handlers = events[name];
|
|
271
|
+
|
|
272
|
+
// Bail out if there are no events stored.
|
|
273
|
+
if (!handlers) break;
|
|
274
|
+
|
|
275
|
+
// Replace events if there are any remaining. Otherwise, clean up.
|
|
276
|
+
var remaining = [];
|
|
277
|
+
for (var j = 0; j < handlers.length; j++) {
|
|
278
|
+
var handler = handlers[j];
|
|
279
|
+
if (
|
|
280
|
+
callback && callback !== handler.callback &&
|
|
281
|
+
callback !== handler.callback._callback ||
|
|
282
|
+
context && context !== handler.context
|
|
283
|
+
) {
|
|
284
|
+
remaining.push(handler);
|
|
285
|
+
} else {
|
|
286
|
+
listening = handler.listening;
|
|
287
|
+
if (listening && --listening.count === 0) {
|
|
288
|
+
delete listeners[listening.id];
|
|
289
|
+
delete listening.listeningTo[listening.objId];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
193
292
|
}
|
|
194
|
-
|
|
293
|
+
|
|
294
|
+
// Update tail event if the list has any events. Otherwise, clean up.
|
|
295
|
+
if (remaining.length) {
|
|
296
|
+
events[name] = remaining;
|
|
297
|
+
} else {
|
|
298
|
+
delete events[name];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return events;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// Bind an event to only be triggered a single time. After the first time
|
|
305
|
+
// the callback is invoked, its listener will be removed. If multiple events
|
|
306
|
+
// are passed in using the space-separated syntax, the handler will fire
|
|
307
|
+
// once for each event, not once for a combination of all events.
|
|
308
|
+
Events.once = function(name, callback, context) {
|
|
309
|
+
// Map the event into a `{event: once}` object.
|
|
310
|
+
var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
|
|
311
|
+
if (typeof name === 'string' && context == null) callback = void 0;
|
|
312
|
+
return this.on(events, callback, context);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Inversion-of-control versions of `once`.
|
|
316
|
+
Events.listenToOnce = function(obj, name, callback) {
|
|
317
|
+
// Map the event into a `{event: once}` object.
|
|
318
|
+
var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj));
|
|
319
|
+
return this.listenTo(obj, events);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Reduces the event callbacks into a map of `{event: onceWrapper}`.
|
|
323
|
+
// `offer` unbinds the `onceWrapper` after it has been called.
|
|
324
|
+
var onceMap = function(map, name, callback, offer) {
|
|
325
|
+
if (callback) {
|
|
326
|
+
var once = map[name] = _.once(function() {
|
|
327
|
+
offer(name, once);
|
|
328
|
+
callback.apply(this, arguments);
|
|
329
|
+
});
|
|
330
|
+
once._callback = callback;
|
|
195
331
|
}
|
|
332
|
+
return map;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Trigger one or many events, firing all bound callbacks. Callbacks are
|
|
336
|
+
// passed the same arguments as `trigger` is, apart from the event name
|
|
337
|
+
// (unless you're listening on `"all"`, which will cause your callback to
|
|
338
|
+
// receive the true name of the event as the first argument).
|
|
339
|
+
Events.trigger = function(name) {
|
|
340
|
+
if (!this._events) return this;
|
|
341
|
+
|
|
342
|
+
var length = Math.max(0, arguments.length - 1);
|
|
343
|
+
var args = Array(length);
|
|
344
|
+
for (var i = 0; i < length; i++) args[i] = arguments[i + 1];
|
|
345
|
+
|
|
346
|
+
eventsApi(triggerApi, this._events, name, void 0, args);
|
|
347
|
+
return this;
|
|
348
|
+
};
|
|
196
349
|
|
|
197
|
-
|
|
350
|
+
// Handles triggering the appropriate event callbacks.
|
|
351
|
+
var triggerApi = function(objEvents, name, callback, args) {
|
|
352
|
+
if (objEvents) {
|
|
353
|
+
var events = objEvents[name];
|
|
354
|
+
var allEvents = objEvents.all;
|
|
355
|
+
if (events && allEvents) allEvents = allEvents.slice();
|
|
356
|
+
if (events) triggerEvents(events, args);
|
|
357
|
+
if (allEvents) triggerEvents(allEvents, [name].concat(args));
|
|
358
|
+
}
|
|
359
|
+
return objEvents;
|
|
198
360
|
};
|
|
199
361
|
|
|
200
362
|
// A difficult-to-believe, but optimized internal dispatch function for
|
|
@@ -211,22 +373,6 @@
|
|
|
211
373
|
}
|
|
212
374
|
};
|
|
213
375
|
|
|
214
|
-
var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
|
|
215
|
-
|
|
216
|
-
// Inversion-of-control versions of `on` and `once`. Tell *this* object to
|
|
217
|
-
// listen to an event in another object ... keeping track of what it's
|
|
218
|
-
// listening to.
|
|
219
|
-
_.each(listenMethods, function(implementation, method) {
|
|
220
|
-
Events[method] = function(obj, name, callback) {
|
|
221
|
-
var listeningTo = this._listeningTo || (this._listeningTo = {});
|
|
222
|
-
var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
|
|
223
|
-
listeningTo[id] = obj;
|
|
224
|
-
if (!callback && typeof name === 'object') callback = this;
|
|
225
|
-
obj[implementation](name, callback, this);
|
|
226
|
-
return this;
|
|
227
|
-
};
|
|
228
|
-
});
|
|
229
|
-
|
|
230
376
|
// Aliases for backwards compatibility.
|
|
231
377
|
Events.bind = Events.on;
|
|
232
378
|
Events.unbind = Events.off;
|
|
@@ -248,11 +394,12 @@
|
|
|
248
394
|
var Model = Backbone.Model = function(attributes, options) {
|
|
249
395
|
var attrs = attributes || {};
|
|
250
396
|
options || (options = {});
|
|
251
|
-
this.cid = _.uniqueId(
|
|
397
|
+
this.cid = _.uniqueId(this.cidPrefix);
|
|
252
398
|
this.attributes = {};
|
|
253
399
|
if (options.collection) this.collection = options.collection;
|
|
254
400
|
if (options.parse) attrs = this.parse(attrs, options) || {};
|
|
255
|
-
|
|
401
|
+
var defaults = _.result(this, 'defaults');
|
|
402
|
+
attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
|
|
256
403
|
this.set(attrs, options);
|
|
257
404
|
this.changed = {};
|
|
258
405
|
this.initialize.apply(this, arguments);
|
|
@@ -271,6 +418,10 @@
|
|
|
271
418
|
// CouchDB users may want to set this to `"_id"`.
|
|
272
419
|
idAttribute: 'id',
|
|
273
420
|
|
|
421
|
+
// The prefix is used to create the client id which is used to identify models locally.
|
|
422
|
+
// You may want to override this if you're experiencing name clashes with model ids.
|
|
423
|
+
cidPrefix: 'c',
|
|
424
|
+
|
|
274
425
|
// Initialize is an empty function by default. Override it with your own
|
|
275
426
|
// initialization logic.
|
|
276
427
|
initialize: function(){},
|
|
@@ -302,14 +453,19 @@
|
|
|
302
453
|
return this.get(attr) != null;
|
|
303
454
|
},
|
|
304
455
|
|
|
456
|
+
// Special-cased proxy to underscore's `_.matches` method.
|
|
457
|
+
matches: function(attrs) {
|
|
458
|
+
return !!_.iteratee(attrs, this)(this.attributes);
|
|
459
|
+
},
|
|
460
|
+
|
|
305
461
|
// Set a hash of model attributes on the object, firing `"change"`. This is
|
|
306
462
|
// the core primitive operation of a model, updating the data and notifying
|
|
307
463
|
// anyone who needs to know about the change in state. The heart of the beast.
|
|
308
464
|
set: function(key, val, options) {
|
|
309
|
-
var attr, attrs, unset, changes, silent, changing, prev, current;
|
|
310
465
|
if (key == null) return this;
|
|
311
466
|
|
|
312
467
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
|
468
|
+
var attrs;
|
|
313
469
|
if (typeof key === 'object') {
|
|
314
470
|
attrs = key;
|
|
315
471
|
options = val;
|
|
@@ -323,37 +479,40 @@
|
|
|
323
479
|
if (!this._validate(attrs, options)) return false;
|
|
324
480
|
|
|
325
481
|
// Extract attributes and options.
|
|
326
|
-
unset
|
|
327
|
-
silent
|
|
328
|
-
changes
|
|
329
|
-
changing
|
|
330
|
-
this._changing
|
|
482
|
+
var unset = options.unset;
|
|
483
|
+
var silent = options.silent;
|
|
484
|
+
var changes = [];
|
|
485
|
+
var changing = this._changing;
|
|
486
|
+
this._changing = true;
|
|
331
487
|
|
|
332
488
|
if (!changing) {
|
|
333
489
|
this._previousAttributes = _.clone(this.attributes);
|
|
334
490
|
this.changed = {};
|
|
335
491
|
}
|
|
336
|
-
current = this.attributes, prev = this._previousAttributes;
|
|
337
492
|
|
|
338
|
-
|
|
339
|
-
|
|
493
|
+
var current = this.attributes;
|
|
494
|
+
var changed = this.changed;
|
|
495
|
+
var prev = this._previousAttributes;
|
|
340
496
|
|
|
341
497
|
// For each `set` attribute, update or delete the current value.
|
|
342
|
-
for (attr in attrs) {
|
|
498
|
+
for (var attr in attrs) {
|
|
343
499
|
val = attrs[attr];
|
|
344
500
|
if (!_.isEqual(current[attr], val)) changes.push(attr);
|
|
345
501
|
if (!_.isEqual(prev[attr], val)) {
|
|
346
|
-
|
|
502
|
+
changed[attr] = val;
|
|
347
503
|
} else {
|
|
348
|
-
delete
|
|
504
|
+
delete changed[attr];
|
|
349
505
|
}
|
|
350
506
|
unset ? delete current[attr] : current[attr] = val;
|
|
351
507
|
}
|
|
352
508
|
|
|
509
|
+
// Update the `id`.
|
|
510
|
+
if (this.idAttribute in attrs) this.id = this.get(this.idAttribute);
|
|
511
|
+
|
|
353
512
|
// Trigger all relevant attribute changes.
|
|
354
513
|
if (!silent) {
|
|
355
514
|
if (changes.length) this._pending = options;
|
|
356
|
-
for (var i = 0
|
|
515
|
+
for (var i = 0; i < changes.length; i++) {
|
|
357
516
|
this.trigger('change:' + changes[i], this, current[changes[i]], options);
|
|
358
517
|
}
|
|
359
518
|
}
|
|
@@ -401,13 +560,14 @@
|
|
|
401
560
|
// determining if there *would be* a change.
|
|
402
561
|
changedAttributes: function(diff) {
|
|
403
562
|
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
|
|
404
|
-
var val, changed = false;
|
|
405
563
|
var old = this._changing ? this._previousAttributes : this.attributes;
|
|
564
|
+
var changed = {};
|
|
406
565
|
for (var attr in diff) {
|
|
407
|
-
|
|
408
|
-
|
|
566
|
+
var val = diff[attr];
|
|
567
|
+
if (_.isEqual(old[attr], val)) continue;
|
|
568
|
+
changed[attr] = val;
|
|
409
569
|
}
|
|
410
|
-
return changed;
|
|
570
|
+
return _.size(changed) ? changed : false;
|
|
411
571
|
},
|
|
412
572
|
|
|
413
573
|
// Get the previous value of an attribute, recorded at the time the last
|
|
@@ -423,17 +583,16 @@
|
|
|
423
583
|
return _.clone(this._previousAttributes);
|
|
424
584
|
},
|
|
425
585
|
|
|
426
|
-
// Fetch the model from the server
|
|
427
|
-
//
|
|
428
|
-
// triggering a `"change"` event.
|
|
586
|
+
// Fetch the model from the server, merging the response with the model's
|
|
587
|
+
// local attributes. Any changed attributes will trigger a "change" event.
|
|
429
588
|
fetch: function(options) {
|
|
430
|
-
options =
|
|
431
|
-
if (options.parse === void 0) options.parse = true;
|
|
589
|
+
options = _.extend({parse: true}, options);
|
|
432
590
|
var model = this;
|
|
433
591
|
var success = options.success;
|
|
434
592
|
options.success = function(resp) {
|
|
435
|
-
|
|
436
|
-
if (
|
|
593
|
+
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
|
|
594
|
+
if (!model.set(serverAttrs, options)) return false;
|
|
595
|
+
if (success) success.call(options.context, model, resp, options);
|
|
437
596
|
model.trigger('sync', model, resp, options);
|
|
438
597
|
};
|
|
439
598
|
wrapError(this, options);
|
|
@@ -444,9 +603,8 @@
|
|
|
444
603
|
// If the server returns an attributes hash that differs, the model's
|
|
445
604
|
// state will be `set` again.
|
|
446
605
|
save: function(key, val, options) {
|
|
447
|
-
var attrs, method, xhr, attributes = this.attributes;
|
|
448
|
-
|
|
449
606
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
|
607
|
+
var attrs;
|
|
450
608
|
if (key == null || typeof key === 'object') {
|
|
451
609
|
attrs = key;
|
|
452
610
|
options = val;
|
|
@@ -454,46 +612,43 @@
|
|
|
454
612
|
(attrs = {})[key] = val;
|
|
455
613
|
}
|
|
456
614
|
|
|
457
|
-
options = _.extend({validate: true}, options);
|
|
615
|
+
options = _.extend({validate: true, parse: true}, options);
|
|
616
|
+
var wait = options.wait;
|
|
458
617
|
|
|
459
618
|
// If we're not waiting and attributes exist, save acts as
|
|
460
619
|
// `set(attr).save(null, opts)` with validation. Otherwise, check if
|
|
461
620
|
// the model will be valid when the attributes, if any, are set.
|
|
462
|
-
if (attrs && !
|
|
621
|
+
if (attrs && !wait) {
|
|
463
622
|
if (!this.set(attrs, options)) return false;
|
|
464
|
-
} else {
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Set temporary attributes if `{wait: true}`.
|
|
469
|
-
if (attrs && options.wait) {
|
|
470
|
-
this.attributes = _.extend({}, attributes, attrs);
|
|
623
|
+
} else if (!this._validate(attrs, options)) {
|
|
624
|
+
return false;
|
|
471
625
|
}
|
|
472
626
|
|
|
473
627
|
// After a successful server-side save, the client is (optionally)
|
|
474
628
|
// updated with the server-side state.
|
|
475
|
-
if (options.parse === void 0) options.parse = true;
|
|
476
629
|
var model = this;
|
|
477
630
|
var success = options.success;
|
|
631
|
+
var attributes = this.attributes;
|
|
478
632
|
options.success = function(resp) {
|
|
479
633
|
// Ensure attributes are restored during synchronous saves.
|
|
480
634
|
model.attributes = attributes;
|
|
481
|
-
var serverAttrs = model.parse(resp, options);
|
|
482
|
-
if (
|
|
483
|
-
if (
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
if (success) success(model, resp, options);
|
|
635
|
+
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
|
|
636
|
+
if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
|
|
637
|
+
if (serverAttrs && !model.set(serverAttrs, options)) return false;
|
|
638
|
+
if (success) success.call(options.context, model, resp, options);
|
|
487
639
|
model.trigger('sync', model, resp, options);
|
|
488
640
|
};
|
|
489
641
|
wrapError(this, options);
|
|
490
642
|
|
|
491
|
-
|
|
492
|
-
if (
|
|
493
|
-
|
|
643
|
+
// Set temporary attributes if `{wait: true}` to properly find new ids.
|
|
644
|
+
if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
|
|
645
|
+
|
|
646
|
+
var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
|
|
647
|
+
if (method === 'patch' && !options.attrs) options.attrs = attrs;
|
|
648
|
+
var xhr = this.sync(method, this, options);
|
|
494
649
|
|
|
495
650
|
// Restore attributes.
|
|
496
|
-
|
|
651
|
+
this.attributes = attributes;
|
|
497
652
|
|
|
498
653
|
return xhr;
|
|
499
654
|
},
|
|
@@ -505,25 +660,27 @@
|
|
|
505
660
|
options = options ? _.clone(options) : {};
|
|
506
661
|
var model = this;
|
|
507
662
|
var success = options.success;
|
|
663
|
+
var wait = options.wait;
|
|
508
664
|
|
|
509
665
|
var destroy = function() {
|
|
666
|
+
model.stopListening();
|
|
510
667
|
model.trigger('destroy', model, model.collection, options);
|
|
511
668
|
};
|
|
512
669
|
|
|
513
670
|
options.success = function(resp) {
|
|
514
|
-
if (
|
|
515
|
-
if (success) success(model, resp, options);
|
|
671
|
+
if (wait) destroy();
|
|
672
|
+
if (success) success.call(options.context, model, resp, options);
|
|
516
673
|
if (!model.isNew()) model.trigger('sync', model, resp, options);
|
|
517
674
|
};
|
|
518
675
|
|
|
676
|
+
var xhr = false;
|
|
519
677
|
if (this.isNew()) {
|
|
520
|
-
options.success
|
|
521
|
-
|
|
678
|
+
_.defer(options.success);
|
|
679
|
+
} else {
|
|
680
|
+
wrapError(this, options);
|
|
681
|
+
xhr = this.sync('delete', this, options);
|
|
522
682
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
var xhr = this.sync('delete', this, options);
|
|
526
|
-
if (!options.wait) destroy();
|
|
683
|
+
if (!wait) destroy();
|
|
527
684
|
return xhr;
|
|
528
685
|
},
|
|
529
686
|
|
|
@@ -536,7 +693,8 @@
|
|
|
536
693
|
_.result(this.collection, 'url') ||
|
|
537
694
|
urlError();
|
|
538
695
|
if (this.isNew()) return base;
|
|
539
|
-
|
|
696
|
+
var id = this.get(this.idAttribute);
|
|
697
|
+
return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
|
|
540
698
|
},
|
|
541
699
|
|
|
542
700
|
// **parse** converts a response into the hash of attributes to be `set` on
|
|
@@ -557,7 +715,7 @@
|
|
|
557
715
|
|
|
558
716
|
// Check if the model is currently in a valid state.
|
|
559
717
|
isValid: function(options) {
|
|
560
|
-
return this._validate({}, _.extend(
|
|
718
|
+
return this._validate({}, _.extend({}, options, {validate: true}));
|
|
561
719
|
},
|
|
562
720
|
|
|
563
721
|
// Run validation against the next complete set of model attributes,
|
|
@@ -573,23 +731,19 @@
|
|
|
573
731
|
|
|
574
732
|
});
|
|
575
733
|
|
|
576
|
-
// Underscore methods that we want to implement on the Model
|
|
577
|
-
|
|
734
|
+
// Underscore methods that we want to implement on the Model, mapped to the
|
|
735
|
+
// number of arguments they take.
|
|
736
|
+
var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
|
|
737
|
+
omit: 0, chain: 1, isEmpty: 1};
|
|
578
738
|
|
|
579
739
|
// Mix in each Underscore method as a proxy to `Model#attributes`.
|
|
580
|
-
|
|
581
|
-
Model.prototype[method] = function() {
|
|
582
|
-
var args = slice.call(arguments);
|
|
583
|
-
args.unshift(this.attributes);
|
|
584
|
-
return _[method].apply(_, args);
|
|
585
|
-
};
|
|
586
|
-
});
|
|
740
|
+
addUnderscoreMethods(Model, modelMethods, 'attributes');
|
|
587
741
|
|
|
588
742
|
// Backbone.Collection
|
|
589
743
|
// -------------------
|
|
590
744
|
|
|
591
745
|
// If models tend to represent a single row of data, a Backbone Collection is
|
|
592
|
-
// more
|
|
746
|
+
// more analogous to a table full of data ... or a small slice or page of that
|
|
593
747
|
// table, or a collection of rows that belong together for a particular reason
|
|
594
748
|
// -- all of the messages in this particular folder, all of the documents
|
|
595
749
|
// belonging to this particular author, and so on. Collections maintain
|
|
@@ -611,6 +765,17 @@
|
|
|
611
765
|
var setOptions = {add: true, remove: true, merge: true};
|
|
612
766
|
var addOptions = {add: true, remove: false};
|
|
613
767
|
|
|
768
|
+
// Splices `insert` into `array` at index `at`.
|
|
769
|
+
var splice = function(array, insert, at) {
|
|
770
|
+
at = Math.min(Math.max(at, 0), array.length);
|
|
771
|
+
var tail = Array(array.length - at);
|
|
772
|
+
var length = insert.length;
|
|
773
|
+
var i;
|
|
774
|
+
for (i = 0; i < tail.length; i++) tail[i] = array[i + at];
|
|
775
|
+
for (i = 0; i < length; i++) array[i + at] = insert[i];
|
|
776
|
+
for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
|
|
777
|
+
};
|
|
778
|
+
|
|
614
779
|
// Define the Collection's inheritable methods.
|
|
615
780
|
_.extend(Collection.prototype, Events, {
|
|
616
781
|
|
|
@@ -625,7 +790,7 @@
|
|
|
625
790
|
// The JSON representation of a Collection is an array of the
|
|
626
791
|
// models' attributes.
|
|
627
792
|
toJSON: function(options) {
|
|
628
|
-
return this.map(function(model){ return model.toJSON(options); });
|
|
793
|
+
return this.map(function(model) { return model.toJSON(options); });
|
|
629
794
|
},
|
|
630
795
|
|
|
631
796
|
// Proxy `Backbone.sync` by default.
|
|
@@ -633,32 +798,24 @@
|
|
|
633
798
|
return Backbone.sync.apply(this, arguments);
|
|
634
799
|
},
|
|
635
800
|
|
|
636
|
-
// Add a model, or list of models to the set.
|
|
801
|
+
// Add a model, or list of models to the set. `models` may be Backbone
|
|
802
|
+
// Models or raw JavaScript objects to be converted to Models, or any
|
|
803
|
+
// combination of the two.
|
|
637
804
|
add: function(models, options) {
|
|
638
805
|
return this.set(models, _.extend({merge: false}, options, addOptions));
|
|
639
806
|
},
|
|
640
807
|
|
|
641
808
|
// Remove a model, or a list of models from the set.
|
|
642
809
|
remove: function(models, options) {
|
|
810
|
+
options = _.extend({}, options);
|
|
643
811
|
var singular = !_.isArray(models);
|
|
644
|
-
models = singular ? [models] :
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if (!model) continue;
|
|
650
|
-
delete this._byId[model.id];
|
|
651
|
-
delete this._byId[model.cid];
|
|
652
|
-
index = this.indexOf(model);
|
|
653
|
-
this.models.splice(index, 1);
|
|
654
|
-
this.length--;
|
|
655
|
-
if (!options.silent) {
|
|
656
|
-
options.index = index;
|
|
657
|
-
model.trigger('remove', model, this, options);
|
|
658
|
-
}
|
|
659
|
-
this._removeReference(model, options);
|
|
812
|
+
models = singular ? [models] : models.slice();
|
|
813
|
+
var removed = this._removeModels(models, options);
|
|
814
|
+
if (!options.silent && removed.length) {
|
|
815
|
+
options.changes = {added: [], merged: [], removed: removed};
|
|
816
|
+
this.trigger('update', this, options);
|
|
660
817
|
}
|
|
661
|
-
return singular ?
|
|
818
|
+
return singular ? removed[0] : removed;
|
|
662
819
|
},
|
|
663
820
|
|
|
664
821
|
// Update a collection by `set`-ing a new list of models, adding new ones,
|
|
@@ -666,89 +823,114 @@
|
|
|
666
823
|
// already exist in the collection, as necessary. Similar to **Model#set**,
|
|
667
824
|
// the core operation for updating the data contained by the collection.
|
|
668
825
|
set: function(models, options) {
|
|
669
|
-
|
|
670
|
-
|
|
826
|
+
if (models == null) return;
|
|
827
|
+
|
|
828
|
+
options = _.extend({}, setOptions, options);
|
|
829
|
+
if (options.parse && !this._isModel(models)) {
|
|
830
|
+
models = this.parse(models, options) || [];
|
|
831
|
+
}
|
|
832
|
+
|
|
671
833
|
var singular = !_.isArray(models);
|
|
672
|
-
models = singular ?
|
|
673
|
-
|
|
834
|
+
models = singular ? [models] : models.slice();
|
|
835
|
+
|
|
674
836
|
var at = options.at;
|
|
675
|
-
|
|
676
|
-
|
|
837
|
+
if (at != null) at = +at;
|
|
838
|
+
if (at > this.length) at = this.length;
|
|
839
|
+
if (at < 0) at += this.length + 1;
|
|
840
|
+
|
|
841
|
+
var set = [];
|
|
842
|
+
var toAdd = [];
|
|
843
|
+
var toMerge = [];
|
|
844
|
+
var toRemove = [];
|
|
845
|
+
var modelMap = {};
|
|
846
|
+
|
|
847
|
+
var add = options.add;
|
|
848
|
+
var merge = options.merge;
|
|
849
|
+
var remove = options.remove;
|
|
850
|
+
|
|
851
|
+
var sort = false;
|
|
852
|
+
var sortable = this.comparator && at == null && options.sort !== false;
|
|
677
853
|
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
|
|
678
|
-
var toAdd = [], toRemove = [], modelMap = {};
|
|
679
|
-
var add = options.add, merge = options.merge, remove = options.remove;
|
|
680
|
-
var order = !sortable && add && remove ? [] : false;
|
|
681
854
|
|
|
682
855
|
// Turn bare objects into model references, and prevent invalid models
|
|
683
856
|
// from being added.
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
id = model = attrs;
|
|
688
|
-
} else {
|
|
689
|
-
id = attrs[targetModel.prototype.idAttribute || 'id'];
|
|
690
|
-
}
|
|
857
|
+
var model, i;
|
|
858
|
+
for (i = 0; i < models.length; i++) {
|
|
859
|
+
model = models[i];
|
|
691
860
|
|
|
692
861
|
// If a duplicate is found, prevent it from being added and
|
|
693
862
|
// optionally merge it into the existing model.
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
if (merge) {
|
|
697
|
-
attrs =
|
|
863
|
+
var existing = this.get(model);
|
|
864
|
+
if (existing) {
|
|
865
|
+
if (merge && model !== existing) {
|
|
866
|
+
var attrs = this._isModel(model) ? model.attributes : model;
|
|
698
867
|
if (options.parse) attrs = existing.parse(attrs, options);
|
|
699
868
|
existing.set(attrs, options);
|
|
700
|
-
|
|
869
|
+
toMerge.push(existing);
|
|
870
|
+
if (sortable && !sort) sort = existing.hasChanged(sortAttr);
|
|
871
|
+
}
|
|
872
|
+
if (!modelMap[existing.cid]) {
|
|
873
|
+
modelMap[existing.cid] = true;
|
|
874
|
+
set.push(existing);
|
|
701
875
|
}
|
|
702
876
|
models[i] = existing;
|
|
703
877
|
|
|
704
878
|
// If this is a new, valid model, push it to the `toAdd` list.
|
|
705
879
|
} else if (add) {
|
|
706
|
-
model = models[i] = this._prepareModel(
|
|
707
|
-
if (
|
|
708
|
-
|
|
709
|
-
|
|
880
|
+
model = models[i] = this._prepareModel(model, options);
|
|
881
|
+
if (model) {
|
|
882
|
+
toAdd.push(model);
|
|
883
|
+
this._addReference(model, options);
|
|
884
|
+
modelMap[model.cid] = true;
|
|
885
|
+
set.push(model);
|
|
886
|
+
}
|
|
710
887
|
}
|
|
711
|
-
|
|
712
|
-
// Do not add multiple models with the same `id`.
|
|
713
|
-
model = existing || model;
|
|
714
|
-
if (order && (model.isNew() || !modelMap[model.id])) order.push(model);
|
|
715
|
-
modelMap[model.id] = true;
|
|
716
888
|
}
|
|
717
889
|
|
|
718
|
-
// Remove
|
|
890
|
+
// Remove stale models.
|
|
719
891
|
if (remove) {
|
|
720
|
-
for (i = 0
|
|
721
|
-
|
|
892
|
+
for (i = 0; i < this.length; i++) {
|
|
893
|
+
model = this.models[i];
|
|
894
|
+
if (!modelMap[model.cid]) toRemove.push(model);
|
|
722
895
|
}
|
|
723
|
-
if (toRemove.length) this.
|
|
896
|
+
if (toRemove.length) this._removeModels(toRemove, options);
|
|
724
897
|
}
|
|
725
898
|
|
|
726
899
|
// See if sorting is needed, update `length` and splice in new models.
|
|
727
|
-
|
|
900
|
+
var orderChanged = false;
|
|
901
|
+
var replace = !sortable && add && remove;
|
|
902
|
+
if (set.length && replace) {
|
|
903
|
+
orderChanged = this.length !== set.length || _.some(this.models, function(m, index) {
|
|
904
|
+
return m !== set[index];
|
|
905
|
+
});
|
|
906
|
+
this.models.length = 0;
|
|
907
|
+
splice(this.models, set, 0);
|
|
908
|
+
this.length = this.models.length;
|
|
909
|
+
} else if (toAdd.length) {
|
|
728
910
|
if (sortable) sort = true;
|
|
729
|
-
this.
|
|
730
|
-
|
|
731
|
-
for (i = 0, l = toAdd.length; i < l; i++) {
|
|
732
|
-
this.models.splice(at + i, 0, toAdd[i]);
|
|
733
|
-
}
|
|
734
|
-
} else {
|
|
735
|
-
if (order) this.models.length = 0;
|
|
736
|
-
var orderedModels = order || toAdd;
|
|
737
|
-
for (i = 0, l = orderedModels.length; i < l; i++) {
|
|
738
|
-
this.models.push(orderedModels[i]);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
911
|
+
splice(this.models, toAdd, at == null ? this.length : at);
|
|
912
|
+
this.length = this.models.length;
|
|
741
913
|
}
|
|
742
914
|
|
|
743
915
|
// Silently sort the collection if appropriate.
|
|
744
916
|
if (sort) this.sort({silent: true});
|
|
745
917
|
|
|
746
|
-
// Unless silenced, it's time to fire all appropriate add/sort events.
|
|
918
|
+
// Unless silenced, it's time to fire all appropriate add/sort/update events.
|
|
747
919
|
if (!options.silent) {
|
|
748
|
-
for (i = 0
|
|
749
|
-
(
|
|
920
|
+
for (i = 0; i < toAdd.length; i++) {
|
|
921
|
+
if (at != null) options.index = at + i;
|
|
922
|
+
model = toAdd[i];
|
|
923
|
+
model.trigger('add', model, this, options);
|
|
924
|
+
}
|
|
925
|
+
if (sort || orderChanged) this.trigger('sort', this, options);
|
|
926
|
+
if (toAdd.length || toRemove.length || toMerge.length) {
|
|
927
|
+
options.changes = {
|
|
928
|
+
added: toAdd,
|
|
929
|
+
removed: toRemove,
|
|
930
|
+
merged: toMerge
|
|
931
|
+
};
|
|
932
|
+
this.trigger('update', this, options);
|
|
750
933
|
}
|
|
751
|
-
if (sort || (order && order.length)) this.trigger('sort', this, options);
|
|
752
934
|
}
|
|
753
935
|
|
|
754
936
|
// Return the added (or merged) model (or models).
|
|
@@ -760,8 +942,8 @@
|
|
|
760
942
|
// any granular `add` or `remove` events. Fires `reset` when finished.
|
|
761
943
|
// Useful for bulk operations and optimizations.
|
|
762
944
|
reset: function(models, options) {
|
|
763
|
-
options
|
|
764
|
-
for (var i = 0
|
|
945
|
+
options = options ? _.clone(options) : {};
|
|
946
|
+
for (var i = 0; i < this.models.length; i++) {
|
|
765
947
|
this._removeReference(this.models[i], options);
|
|
766
948
|
}
|
|
767
949
|
options.previousModels = this.models;
|
|
@@ -779,8 +961,7 @@
|
|
|
779
961
|
// Remove a model from the end of the collection.
|
|
780
962
|
pop: function(options) {
|
|
781
963
|
var model = this.at(this.length - 1);
|
|
782
|
-
this.remove(model, options);
|
|
783
|
-
return model;
|
|
964
|
+
return this.remove(model, options);
|
|
784
965
|
},
|
|
785
966
|
|
|
786
967
|
// Add a model to the beginning of the collection.
|
|
@@ -791,8 +972,7 @@
|
|
|
791
972
|
// Remove a model from the beginning of the collection.
|
|
792
973
|
shift: function(options) {
|
|
793
974
|
var model = this.at(0);
|
|
794
|
-
this.remove(model, options);
|
|
795
|
-
return model;
|
|
975
|
+
return this.remove(model, options);
|
|
796
976
|
},
|
|
797
977
|
|
|
798
978
|
// Slice out a sub-array of models from the collection.
|
|
@@ -800,27 +980,30 @@
|
|
|
800
980
|
return slice.apply(this.models, arguments);
|
|
801
981
|
},
|
|
802
982
|
|
|
803
|
-
// Get a model from the set by id
|
|
983
|
+
// Get a model from the set by id, cid, model object with id or cid
|
|
984
|
+
// properties, or an attributes object that is transformed through modelId.
|
|
804
985
|
get: function(obj) {
|
|
805
986
|
if (obj == null) return void 0;
|
|
806
|
-
return this._byId[obj] ||
|
|
987
|
+
return this._byId[obj] ||
|
|
988
|
+
this._byId[this.modelId(obj.attributes || obj)] ||
|
|
989
|
+
obj.cid && this._byId[obj.cid];
|
|
990
|
+
},
|
|
991
|
+
|
|
992
|
+
// Returns `true` if the model is in the collection.
|
|
993
|
+
has: function(obj) {
|
|
994
|
+
return this.get(obj) != null;
|
|
807
995
|
},
|
|
808
996
|
|
|
809
997
|
// Get the model at the given index.
|
|
810
998
|
at: function(index) {
|
|
999
|
+
if (index < 0) index += this.length;
|
|
811
1000
|
return this.models[index];
|
|
812
1001
|
},
|
|
813
1002
|
|
|
814
1003
|
// Return models with matching attributes. Useful for simple cases of
|
|
815
1004
|
// `filter`.
|
|
816
1005
|
where: function(attrs, first) {
|
|
817
|
-
|
|
818
|
-
return this[first ? 'find' : 'filter'](function(model) {
|
|
819
|
-
for (var key in attrs) {
|
|
820
|
-
if (attrs[key] !== model.get(key)) return false;
|
|
821
|
-
}
|
|
822
|
-
return true;
|
|
823
|
-
});
|
|
1006
|
+
return this[first ? 'find' : 'filter'](attrs);
|
|
824
1007
|
},
|
|
825
1008
|
|
|
826
1009
|
// Return the first model with matching attributes. Useful for simple cases
|
|
@@ -833,37 +1016,39 @@
|
|
|
833
1016
|
// normal circumstances, as the set will maintain sort order as each item
|
|
834
1017
|
// is added.
|
|
835
1018
|
sort: function(options) {
|
|
836
|
-
|
|
1019
|
+
var comparator = this.comparator;
|
|
1020
|
+
if (!comparator) throw new Error('Cannot sort a set without a comparator');
|
|
837
1021
|
options || (options = {});
|
|
838
1022
|
|
|
1023
|
+
var length = comparator.length;
|
|
1024
|
+
if (_.isFunction(comparator)) comparator = _.bind(comparator, this);
|
|
1025
|
+
|
|
839
1026
|
// Run sort based on type of `comparator`.
|
|
840
|
-
if (_.isString(
|
|
841
|
-
this.models = this.sortBy(
|
|
1027
|
+
if (length === 1 || _.isString(comparator)) {
|
|
1028
|
+
this.models = this.sortBy(comparator);
|
|
842
1029
|
} else {
|
|
843
|
-
this.models.sort(
|
|
1030
|
+
this.models.sort(comparator);
|
|
844
1031
|
}
|
|
845
|
-
|
|
846
1032
|
if (!options.silent) this.trigger('sort', this, options);
|
|
847
1033
|
return this;
|
|
848
1034
|
},
|
|
849
1035
|
|
|
850
1036
|
// Pluck an attribute from each model in the collection.
|
|
851
1037
|
pluck: function(attr) {
|
|
852
|
-
return
|
|
1038
|
+
return this.map(attr + '');
|
|
853
1039
|
},
|
|
854
1040
|
|
|
855
1041
|
// Fetch the default set of models for this collection, resetting the
|
|
856
1042
|
// collection when they arrive. If `reset: true` is passed, the response
|
|
857
1043
|
// data will be passed through the `reset` method instead of `set`.
|
|
858
1044
|
fetch: function(options) {
|
|
859
|
-
options =
|
|
860
|
-
if (options.parse === void 0) options.parse = true;
|
|
1045
|
+
options = _.extend({parse: true}, options);
|
|
861
1046
|
var success = options.success;
|
|
862
1047
|
var collection = this;
|
|
863
1048
|
options.success = function(resp) {
|
|
864
1049
|
var method = options.reset ? 'reset' : 'set';
|
|
865
1050
|
collection[method](resp, options);
|
|
866
|
-
if (success) success(collection, resp, options);
|
|
1051
|
+
if (success) success.call(options.context, collection, resp, options);
|
|
867
1052
|
collection.trigger('sync', collection, resp, options);
|
|
868
1053
|
};
|
|
869
1054
|
wrapError(this, options);
|
|
@@ -875,13 +1060,15 @@
|
|
|
875
1060
|
// wait for the server to agree.
|
|
876
1061
|
create: function(model, options) {
|
|
877
1062
|
options = options ? _.clone(options) : {};
|
|
878
|
-
|
|
879
|
-
|
|
1063
|
+
var wait = options.wait;
|
|
1064
|
+
model = this._prepareModel(model, options);
|
|
1065
|
+
if (!model) return false;
|
|
1066
|
+
if (!wait) this.add(model, options);
|
|
880
1067
|
var collection = this;
|
|
881
1068
|
var success = options.success;
|
|
882
|
-
options.success = function(
|
|
883
|
-
if (
|
|
884
|
-
if (success) success(
|
|
1069
|
+
options.success = function(m, resp, callbackOpts) {
|
|
1070
|
+
if (wait) collection.add(m, callbackOpts);
|
|
1071
|
+
if (success) success.call(callbackOpts.context, m, resp, callbackOpts);
|
|
885
1072
|
};
|
|
886
1073
|
model.save(null, options);
|
|
887
1074
|
return model;
|
|
@@ -895,7 +1082,15 @@
|
|
|
895
1082
|
|
|
896
1083
|
// Create a new collection with an identical list of models as this one.
|
|
897
1084
|
clone: function() {
|
|
898
|
-
return new this.constructor(this.models
|
|
1085
|
+
return new this.constructor(this.models, {
|
|
1086
|
+
model: this.model,
|
|
1087
|
+
comparator: this.comparator
|
|
1088
|
+
});
|
|
1089
|
+
},
|
|
1090
|
+
|
|
1091
|
+
// Define how to uniquely identify models in the collection.
|
|
1092
|
+
modelId: function(attrs) {
|
|
1093
|
+
return attrs[this.model.prototype.idAttribute || 'id'];
|
|
899
1094
|
},
|
|
900
1095
|
|
|
901
1096
|
// Private method to reset all internal state. Called when the collection
|
|
@@ -909,7 +1104,10 @@
|
|
|
909
1104
|
// Prepare a hash of attributes (or other model) to be added to this
|
|
910
1105
|
// collection.
|
|
911
1106
|
_prepareModel: function(attrs, options) {
|
|
912
|
-
if (attrs
|
|
1107
|
+
if (this._isModel(attrs)) {
|
|
1108
|
+
if (!attrs.collection) attrs.collection = this;
|
|
1109
|
+
return attrs;
|
|
1110
|
+
}
|
|
913
1111
|
options = options ? _.clone(options) : {};
|
|
914
1112
|
options.collection = this;
|
|
915
1113
|
var model = new this.model(attrs, options);
|
|
@@ -918,16 +1116,53 @@
|
|
|
918
1116
|
return false;
|
|
919
1117
|
},
|
|
920
1118
|
|
|
1119
|
+
// Internal method called by both remove and set.
|
|
1120
|
+
_removeModels: function(models, options) {
|
|
1121
|
+
var removed = [];
|
|
1122
|
+
for (var i = 0; i < models.length; i++) {
|
|
1123
|
+
var model = this.get(models[i]);
|
|
1124
|
+
if (!model) continue;
|
|
1125
|
+
|
|
1126
|
+
var index = this.indexOf(model);
|
|
1127
|
+
this.models.splice(index, 1);
|
|
1128
|
+
this.length--;
|
|
1129
|
+
|
|
1130
|
+
// Remove references before triggering 'remove' event to prevent an
|
|
1131
|
+
// infinite loop. #3693
|
|
1132
|
+
delete this._byId[model.cid];
|
|
1133
|
+
var id = this.modelId(model.attributes);
|
|
1134
|
+
if (id != null) delete this._byId[id];
|
|
1135
|
+
|
|
1136
|
+
if (!options.silent) {
|
|
1137
|
+
options.index = index;
|
|
1138
|
+
model.trigger('remove', model, this, options);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
removed.push(model);
|
|
1142
|
+
this._removeReference(model, options);
|
|
1143
|
+
}
|
|
1144
|
+
return removed;
|
|
1145
|
+
},
|
|
1146
|
+
|
|
1147
|
+
// Method for checking whether an object should be considered a model for
|
|
1148
|
+
// the purposes of adding to the collection.
|
|
1149
|
+
_isModel: function(model) {
|
|
1150
|
+
return model instanceof Model;
|
|
1151
|
+
},
|
|
1152
|
+
|
|
921
1153
|
// Internal method to create a model's ties to a collection.
|
|
922
1154
|
_addReference: function(model, options) {
|
|
923
1155
|
this._byId[model.cid] = model;
|
|
924
|
-
|
|
925
|
-
if (
|
|
1156
|
+
var id = this.modelId(model.attributes);
|
|
1157
|
+
if (id != null) this._byId[id] = model;
|
|
926
1158
|
model.on('all', this._onModelEvent, this);
|
|
927
1159
|
},
|
|
928
1160
|
|
|
929
1161
|
// Internal method to sever a model's ties to a collection.
|
|
930
1162
|
_removeReference: function(model, options) {
|
|
1163
|
+
delete this._byId[model.cid];
|
|
1164
|
+
var id = this.modelId(model.attributes);
|
|
1165
|
+
if (id != null) delete this._byId[id];
|
|
931
1166
|
if (this === model.collection) delete model.collection;
|
|
932
1167
|
model.off('all', this._onModelEvent, this);
|
|
933
1168
|
},
|
|
@@ -937,11 +1172,17 @@
|
|
|
937
1172
|
// events simply proxy through. "add" and "remove" events that originate
|
|
938
1173
|
// in other collections are ignored.
|
|
939
1174
|
_onModelEvent: function(event, model, collection, options) {
|
|
940
|
-
if (
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1175
|
+
if (model) {
|
|
1176
|
+
if ((event === 'add' || event === 'remove') && collection !== this) return;
|
|
1177
|
+
if (event === 'destroy') this.remove(model, options);
|
|
1178
|
+
if (event === 'change') {
|
|
1179
|
+
var prevId = this.modelId(model.previousAttributes());
|
|
1180
|
+
var id = this.modelId(model.attributes);
|
|
1181
|
+
if (prevId !== id) {
|
|
1182
|
+
if (prevId != null) delete this._byId[prevId];
|
|
1183
|
+
if (id != null) this._byId[id] = model;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
945
1186
|
}
|
|
946
1187
|
this.trigger.apply(this, arguments);
|
|
947
1188
|
}
|
|
@@ -951,34 +1192,17 @@
|
|
|
951
1192
|
// Underscore methods that we want to implement on the Collection.
|
|
952
1193
|
// 90% of the core usefulness of Backbone Collections is actually implemented
|
|
953
1194
|
// right here:
|
|
954
|
-
var
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1195
|
+
var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0,
|
|
1196
|
+
foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3,
|
|
1197
|
+
select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
|
|
1198
|
+
contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
|
|
1199
|
+
head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
|
|
1200
|
+
without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
|
|
1201
|
+
isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
|
|
1202
|
+
sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3};
|
|
960
1203
|
|
|
961
1204
|
// Mix in each Underscore method as a proxy to `Collection#models`.
|
|
962
|
-
|
|
963
|
-
Collection.prototype[method] = function() {
|
|
964
|
-
var args = slice.call(arguments);
|
|
965
|
-
args.unshift(this.models);
|
|
966
|
-
return _[method].apply(_, args);
|
|
967
|
-
};
|
|
968
|
-
});
|
|
969
|
-
|
|
970
|
-
// Underscore methods that take a property name as an argument.
|
|
971
|
-
var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];
|
|
972
|
-
|
|
973
|
-
// Use attributes instead of properties.
|
|
974
|
-
_.each(attributeMethods, function(method) {
|
|
975
|
-
Collection.prototype[method] = function(value, context) {
|
|
976
|
-
var iterator = _.isFunction(value) ? value : function(model) {
|
|
977
|
-
return model.get(value);
|
|
978
|
-
};
|
|
979
|
-
return _[method](this.models, iterator, context);
|
|
980
|
-
};
|
|
981
|
-
});
|
|
1205
|
+
addUnderscoreMethods(Collection, collectionMethods, 'models');
|
|
982
1206
|
|
|
983
1207
|
// Backbone.View
|
|
984
1208
|
// -------------
|
|
@@ -995,17 +1219,15 @@
|
|
|
995
1219
|
// if an existing element is not provided...
|
|
996
1220
|
var View = Backbone.View = function(options) {
|
|
997
1221
|
this.cid = _.uniqueId('view');
|
|
998
|
-
options || (options = {});
|
|
999
1222
|
_.extend(this, _.pick(options, viewOptions));
|
|
1000
1223
|
this._ensureElement();
|
|
1001
1224
|
this.initialize.apply(this, arguments);
|
|
1002
|
-
this.delegateEvents();
|
|
1003
1225
|
};
|
|
1004
1226
|
|
|
1005
1227
|
// Cached regex to split keys for `delegate`.
|
|
1006
1228
|
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
|
|
1007
1229
|
|
|
1008
|
-
// List of view options to be
|
|
1230
|
+
// List of view options to be set as properties.
|
|
1009
1231
|
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
|
|
1010
1232
|
|
|
1011
1233
|
// Set up all inheritable **Backbone.View** properties and methods.
|
|
@@ -1034,21 +1256,37 @@
|
|
|
1034
1256
|
// Remove this view by taking the element out of the DOM, and removing any
|
|
1035
1257
|
// applicable Backbone.Events listeners.
|
|
1036
1258
|
remove: function() {
|
|
1037
|
-
this
|
|
1259
|
+
this._removeElement();
|
|
1038
1260
|
this.stopListening();
|
|
1039
1261
|
return this;
|
|
1040
1262
|
},
|
|
1041
1263
|
|
|
1042
|
-
//
|
|
1043
|
-
//
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
this.$el
|
|
1047
|
-
|
|
1048
|
-
|
|
1264
|
+
// Remove this view's element from the document and all event listeners
|
|
1265
|
+
// attached to it. Exposed for subclasses using an alternative DOM
|
|
1266
|
+
// manipulation API.
|
|
1267
|
+
_removeElement: function() {
|
|
1268
|
+
this.$el.remove();
|
|
1269
|
+
},
|
|
1270
|
+
|
|
1271
|
+
// Change the view's element (`this.el` property) and re-delegate the
|
|
1272
|
+
// view's events on the new element.
|
|
1273
|
+
setElement: function(element) {
|
|
1274
|
+
this.undelegateEvents();
|
|
1275
|
+
this._setElement(element);
|
|
1276
|
+
this.delegateEvents();
|
|
1049
1277
|
return this;
|
|
1050
1278
|
},
|
|
1051
1279
|
|
|
1280
|
+
// Creates the `this.el` and `this.$el` references for this view using the
|
|
1281
|
+
// given `el`. `el` can be a CSS selector or an HTML string, a jQuery
|
|
1282
|
+
// context or an element. Subclasses can override this to utilize an
|
|
1283
|
+
// alternative DOM manipulation API and are only required to set the
|
|
1284
|
+
// `this.el` property.
|
|
1285
|
+
_setElement: function(el) {
|
|
1286
|
+
this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
|
|
1287
|
+
this.el = this.$el[0];
|
|
1288
|
+
},
|
|
1289
|
+
|
|
1052
1290
|
// Set callbacks, where `this.events` is a hash of
|
|
1053
1291
|
//
|
|
1054
1292
|
// *{"event selector": "callback"}*
|
|
@@ -1062,37 +1300,49 @@
|
|
|
1062
1300
|
// pairs. Callbacks will be bound to the view, with `this` set properly.
|
|
1063
1301
|
// Uses event delegation for efficiency.
|
|
1064
1302
|
// Omitting the selector binds the event to `this.el`.
|
|
1065
|
-
// This only works for delegate-able events: not `focus`, `blur`, and
|
|
1066
|
-
// not `change`, `submit`, and `reset` in Internet Explorer.
|
|
1067
1303
|
delegateEvents: function(events) {
|
|
1068
|
-
|
|
1304
|
+
events || (events = _.result(this, 'events'));
|
|
1305
|
+
if (!events) return this;
|
|
1069
1306
|
this.undelegateEvents();
|
|
1070
1307
|
for (var key in events) {
|
|
1071
1308
|
var method = events[key];
|
|
1072
|
-
if (!_.isFunction(method)) method = this[
|
|
1309
|
+
if (!_.isFunction(method)) method = this[method];
|
|
1073
1310
|
if (!method) continue;
|
|
1074
|
-
|
|
1075
1311
|
var match = key.match(delegateEventSplitter);
|
|
1076
|
-
|
|
1077
|
-
method = _.bind(method, this);
|
|
1078
|
-
eventName += '.delegateEvents' + this.cid;
|
|
1079
|
-
if (selector === '') {
|
|
1080
|
-
this.$el.on(eventName, method);
|
|
1081
|
-
} else {
|
|
1082
|
-
this.$el.on(eventName, selector, method);
|
|
1083
|
-
}
|
|
1312
|
+
this.delegate(match[1], match[2], _.bind(method, this));
|
|
1084
1313
|
}
|
|
1085
1314
|
return this;
|
|
1086
1315
|
},
|
|
1087
1316
|
|
|
1088
|
-
//
|
|
1317
|
+
// Add a single event listener to the view's element (or a child element
|
|
1318
|
+
// using `selector`). This only works for delegate-able events: not `focus`,
|
|
1319
|
+
// `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
|
|
1320
|
+
delegate: function(eventName, selector, listener) {
|
|
1321
|
+
this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
|
|
1322
|
+
return this;
|
|
1323
|
+
},
|
|
1324
|
+
|
|
1325
|
+
// Clears all callbacks previously bound to the view by `delegateEvents`.
|
|
1089
1326
|
// You usually don't need to use this, but may wish to if you have multiple
|
|
1090
1327
|
// Backbone views attached to the same DOM element.
|
|
1091
1328
|
undelegateEvents: function() {
|
|
1092
|
-
this.$el.off('.delegateEvents' + this.cid);
|
|
1329
|
+
if (this.$el) this.$el.off('.delegateEvents' + this.cid);
|
|
1330
|
+
return this;
|
|
1331
|
+
},
|
|
1332
|
+
|
|
1333
|
+
// A finer-grained `undelegateEvents` for removing a single delegated event.
|
|
1334
|
+
// `selector` and `listener` are both optional.
|
|
1335
|
+
undelegate: function(eventName, selector, listener) {
|
|
1336
|
+
this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
|
|
1093
1337
|
return this;
|
|
1094
1338
|
},
|
|
1095
1339
|
|
|
1340
|
+
// Produces a DOM element to be assigned to your view. Exposed for
|
|
1341
|
+
// subclasses using an alternative DOM manipulation API.
|
|
1342
|
+
_createElement: function(tagName) {
|
|
1343
|
+
return document.createElement(tagName);
|
|
1344
|
+
},
|
|
1345
|
+
|
|
1096
1346
|
// Ensure that the View has a DOM element to render into.
|
|
1097
1347
|
// If `this.el` is a string, pass it through `$()`, take the first
|
|
1098
1348
|
// matching element, and re-assign it to `el`. Otherwise, create
|
|
@@ -1102,11 +1352,17 @@
|
|
|
1102
1352
|
var attrs = _.extend({}, _.result(this, 'attributes'));
|
|
1103
1353
|
if (this.id) attrs.id = _.result(this, 'id');
|
|
1104
1354
|
if (this.className) attrs['class'] = _.result(this, 'className');
|
|
1105
|
-
|
|
1106
|
-
this.
|
|
1355
|
+
this.setElement(this._createElement(_.result(this, 'tagName')));
|
|
1356
|
+
this._setAttributes(attrs);
|
|
1107
1357
|
} else {
|
|
1108
|
-
this.setElement(_.result(this, 'el')
|
|
1358
|
+
this.setElement(_.result(this, 'el'));
|
|
1109
1359
|
}
|
|
1360
|
+
},
|
|
1361
|
+
|
|
1362
|
+
// Set attributes from a hash on this view's element. Exposed for
|
|
1363
|
+
// subclasses using an alternative DOM manipulation API.
|
|
1364
|
+
_setAttributes: function(attributes) {
|
|
1365
|
+
this.$el.attr(attributes);
|
|
1110
1366
|
}
|
|
1111
1367
|
|
|
1112
1368
|
});
|
|
@@ -1175,14 +1431,13 @@
|
|
|
1175
1431
|
params.processData = false;
|
|
1176
1432
|
}
|
|
1177
1433
|
|
|
1178
|
-
//
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
}
|
|
1434
|
+
// Pass along `textStatus` and `errorThrown` from jQuery.
|
|
1435
|
+
var error = options.error;
|
|
1436
|
+
options.error = function(xhr, textStatus, errorThrown) {
|
|
1437
|
+
options.textStatus = textStatus;
|
|
1438
|
+
options.errorThrown = errorThrown;
|
|
1439
|
+
if (error) error.call(options.context, xhr, textStatus, errorThrown);
|
|
1440
|
+
};
|
|
1186
1441
|
|
|
1187
1442
|
// Make the request, allowing the user to override any Ajax options.
|
|
1188
1443
|
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
|
|
@@ -1190,17 +1445,13 @@
|
|
|
1190
1445
|
return xhr;
|
|
1191
1446
|
};
|
|
1192
1447
|
|
|
1193
|
-
var noXhrPatch =
|
|
1194
|
-
typeof window !== 'undefined' && !!window.ActiveXObject &&
|
|
1195
|
-
!(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
|
|
1196
|
-
|
|
1197
1448
|
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
|
|
1198
1449
|
var methodMap = {
|
|
1199
1450
|
'create': 'POST',
|
|
1200
1451
|
'update': 'PUT',
|
|
1201
|
-
'patch':
|
|
1452
|
+
'patch': 'PATCH',
|
|
1202
1453
|
'delete': 'DELETE',
|
|
1203
|
-
'read':
|
|
1454
|
+
'read': 'GET'
|
|
1204
1455
|
};
|
|
1205
1456
|
|
|
1206
1457
|
// Set the default implementation of `Backbone.ajax` to proxy through to `$`.
|
|
@@ -1251,17 +1502,18 @@
|
|
|
1251
1502
|
var router = this;
|
|
1252
1503
|
Backbone.history.route(route, function(fragment) {
|
|
1253
1504
|
var args = router._extractParameters(route, fragment);
|
|
1254
|
-
router.execute(callback, args)
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1505
|
+
if (router.execute(callback, args, name) !== false) {
|
|
1506
|
+
router.trigger.apply(router, ['route:' + name].concat(args));
|
|
1507
|
+
router.trigger('route', name, args);
|
|
1508
|
+
Backbone.history.trigger('route', router, name, args);
|
|
1509
|
+
}
|
|
1258
1510
|
});
|
|
1259
1511
|
return this;
|
|
1260
1512
|
},
|
|
1261
1513
|
|
|
1262
1514
|
// Execute a route handler with the provided parameters. This is an
|
|
1263
1515
|
// excellent place to do pre-route setup or post-route cleanup.
|
|
1264
|
-
execute: function(callback, args) {
|
|
1516
|
+
execute: function(callback, args, name) {
|
|
1265
1517
|
if (callback) callback.apply(this, args);
|
|
1266
1518
|
},
|
|
1267
1519
|
|
|
@@ -1319,7 +1571,7 @@
|
|
|
1319
1571
|
// falls back to polling.
|
|
1320
1572
|
var History = Backbone.History = function() {
|
|
1321
1573
|
this.handlers = [];
|
|
1322
|
-
_.
|
|
1574
|
+
this.checkUrl = _.bind(this.checkUrl, this);
|
|
1323
1575
|
|
|
1324
1576
|
// Ensure that `History` can be used outside of the browser.
|
|
1325
1577
|
if (typeof window !== 'undefined') {
|
|
@@ -1334,12 +1586,6 @@
|
|
|
1334
1586
|
// Cached regex for stripping leading and trailing slashes.
|
|
1335
1587
|
var rootStripper = /^\/+|\/+$/g;
|
|
1336
1588
|
|
|
1337
|
-
// Cached regex for detecting MSIE.
|
|
1338
|
-
var isExplorer = /msie [\w.]+/;
|
|
1339
|
-
|
|
1340
|
-
// Cached regex for removing a trailing slash.
|
|
1341
|
-
var trailingSlash = /\/$/;
|
|
1342
|
-
|
|
1343
1589
|
// Cached regex for stripping urls of hash.
|
|
1344
1590
|
var pathStripper = /#.*$/;
|
|
1345
1591
|
|
|
@@ -1355,7 +1601,29 @@
|
|
|
1355
1601
|
|
|
1356
1602
|
// Are we at the app root?
|
|
1357
1603
|
atRoot: function() {
|
|
1358
|
-
|
|
1604
|
+
var path = this.location.pathname.replace(/[^\/]$/, '$&/');
|
|
1605
|
+
return path === this.root && !this.getSearch();
|
|
1606
|
+
},
|
|
1607
|
+
|
|
1608
|
+
// Does the pathname match the root?
|
|
1609
|
+
matchRoot: function() {
|
|
1610
|
+
var path = this.decodeFragment(this.location.pathname);
|
|
1611
|
+
var rootPath = path.slice(0, this.root.length - 1) + '/';
|
|
1612
|
+
return rootPath === this.root;
|
|
1613
|
+
},
|
|
1614
|
+
|
|
1615
|
+
// Unicode characters in `location.pathname` are percent encoded so they're
|
|
1616
|
+
// decoded for comparison. `%25` should not be decoded since it may be part
|
|
1617
|
+
// of an encoded parameter.
|
|
1618
|
+
decodeFragment: function(fragment) {
|
|
1619
|
+
return decodeURI(fragment.replace(/%25/g, '%2525'));
|
|
1620
|
+
},
|
|
1621
|
+
|
|
1622
|
+
// In IE6, the hash fragment and search params are incorrect if the
|
|
1623
|
+
// fragment contains `?`.
|
|
1624
|
+
getSearch: function() {
|
|
1625
|
+
var match = this.location.href.replace(/#.*/, '').match(/\?.+/);
|
|
1626
|
+
return match ? match[0] : '';
|
|
1359
1627
|
},
|
|
1360
1628
|
|
|
1361
1629
|
// Gets the true hash value. Cannot use location.hash directly due to bug
|
|
@@ -1365,14 +1633,19 @@
|
|
|
1365
1633
|
return match ? match[1] : '';
|
|
1366
1634
|
},
|
|
1367
1635
|
|
|
1368
|
-
// Get the
|
|
1369
|
-
|
|
1370
|
-
|
|
1636
|
+
// Get the pathname and search params, without the root.
|
|
1637
|
+
getPath: function() {
|
|
1638
|
+
var path = this.decodeFragment(
|
|
1639
|
+
this.location.pathname + this.getSearch()
|
|
1640
|
+
).slice(this.root.length - 1);
|
|
1641
|
+
return path.charAt(0) === '/' ? path.slice(1) : path;
|
|
1642
|
+
},
|
|
1643
|
+
|
|
1644
|
+
// Get the cross-browser normalized URL fragment from the path or hash.
|
|
1645
|
+
getFragment: function(fragment) {
|
|
1371
1646
|
if (fragment == null) {
|
|
1372
|
-
if (this.
|
|
1373
|
-
fragment =
|
|
1374
|
-
var root = this.root.replace(trailingSlash, '');
|
|
1375
|
-
if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
|
|
1647
|
+
if (this._usePushState || !this._wantsHashChange) {
|
|
1648
|
+
fragment = this.getPath();
|
|
1376
1649
|
} else {
|
|
1377
1650
|
fragment = this.getHash();
|
|
1378
1651
|
}
|
|
@@ -1383,7 +1656,7 @@
|
|
|
1383
1656
|
// Start the hash change handling, returning `true` if the current URL matches
|
|
1384
1657
|
// an existing route, and `false` otherwise.
|
|
1385
1658
|
start: function(options) {
|
|
1386
|
-
if (History.started) throw new Error(
|
|
1659
|
+
if (History.started) throw new Error('Backbone.history has already been started');
|
|
1387
1660
|
History.started = true;
|
|
1388
1661
|
|
|
1389
1662
|
// Figure out the initial configuration. Do we need an iframe?
|
|
@@ -1391,36 +1664,16 @@
|
|
|
1391
1664
|
this.options = _.extend({root: '/'}, this.options, options);
|
|
1392
1665
|
this.root = this.options.root;
|
|
1393
1666
|
this._wantsHashChange = this.options.hashChange !== false;
|
|
1667
|
+
this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
|
|
1668
|
+
this._useHashChange = this._wantsHashChange && this._hasHashChange;
|
|
1394
1669
|
this._wantsPushState = !!this.options.pushState;
|
|
1395
|
-
this._hasPushState = !!(this.
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
|
|
1670
|
+
this._hasPushState = !!(this.history && this.history.pushState);
|
|
1671
|
+
this._usePushState = this._wantsPushState && this._hasPushState;
|
|
1672
|
+
this.fragment = this.getFragment();
|
|
1399
1673
|
|
|
1400
1674
|
// Normalize root to always include a leading and trailing slash.
|
|
1401
1675
|
this.root = ('/' + this.root + '/').replace(rootStripper, '/');
|
|
1402
1676
|
|
|
1403
|
-
if (oldIE && this._wantsHashChange) {
|
|
1404
|
-
var frame = Backbone.$('<iframe src="javascript:0" tabindex="-1">');
|
|
1405
|
-
this.iframe = frame.hide().appendTo('body')[0].contentWindow;
|
|
1406
|
-
this.navigate(fragment);
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
// Depending on whether we're using pushState or hashes, and whether
|
|
1410
|
-
// 'onhashchange' is supported, determine how we check the URL state.
|
|
1411
|
-
if (this._hasPushState) {
|
|
1412
|
-
Backbone.$(window).on('popstate', this.checkUrl);
|
|
1413
|
-
} else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
|
|
1414
|
-
Backbone.$(window).on('hashchange', this.checkUrl);
|
|
1415
|
-
} else if (this._wantsHashChange) {
|
|
1416
|
-
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
// Determine if we need to change the base url, for a pushState link
|
|
1420
|
-
// opened by a non-pushState browser.
|
|
1421
|
-
this.fragment = fragment;
|
|
1422
|
-
var loc = this.location;
|
|
1423
|
-
|
|
1424
1677
|
// Transition from hashChange to pushState or vice versa if both are
|
|
1425
1678
|
// requested.
|
|
1426
1679
|
if (this._wantsHashChange && this._wantsPushState) {
|
|
@@ -1428,27 +1681,75 @@
|
|
|
1428
1681
|
// If we've started off with a route from a `pushState`-enabled
|
|
1429
1682
|
// browser, but we're currently in a browser that doesn't support it...
|
|
1430
1683
|
if (!this._hasPushState && !this.atRoot()) {
|
|
1431
|
-
|
|
1432
|
-
this.location.replace(
|
|
1684
|
+
var rootPath = this.root.slice(0, -1) || '/';
|
|
1685
|
+
this.location.replace(rootPath + '#' + this.getPath());
|
|
1433
1686
|
// Return immediately as browser will do redirect to new url
|
|
1434
1687
|
return true;
|
|
1435
1688
|
|
|
1436
1689
|
// Or if we've started out with a hash-based route, but we're currently
|
|
1437
1690
|
// in a browser where it could be `pushState`-based instead...
|
|
1438
|
-
} else if (this._hasPushState && this.atRoot()
|
|
1439
|
-
this.
|
|
1440
|
-
this.history.replaceState({}, document.title, this.root + this.fragment);
|
|
1691
|
+
} else if (this._hasPushState && this.atRoot()) {
|
|
1692
|
+
this.navigate(this.getHash(), {replace: true});
|
|
1441
1693
|
}
|
|
1442
1694
|
|
|
1443
1695
|
}
|
|
1444
1696
|
|
|
1697
|
+
// Proxy an iframe to handle location events if the browser doesn't
|
|
1698
|
+
// support the `hashchange` event, HTML5 history, or the user wants
|
|
1699
|
+
// `hashChange` but not `pushState`.
|
|
1700
|
+
if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
|
|
1701
|
+
this.iframe = document.createElement('iframe');
|
|
1702
|
+
this.iframe.src = 'javascript:0';
|
|
1703
|
+
this.iframe.style.display = 'none';
|
|
1704
|
+
this.iframe.tabIndex = -1;
|
|
1705
|
+
var body = document.body;
|
|
1706
|
+
// Using `appendChild` will throw on IE < 9 if the document is not ready.
|
|
1707
|
+
var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
|
|
1708
|
+
iWindow.document.open();
|
|
1709
|
+
iWindow.document.close();
|
|
1710
|
+
iWindow.location.hash = '#' + this.fragment;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// Add a cross-platform `addEventListener` shim for older browsers.
|
|
1714
|
+
var addEventListener = window.addEventListener || function(eventName, listener) {
|
|
1715
|
+
return attachEvent('on' + eventName, listener);
|
|
1716
|
+
};
|
|
1717
|
+
|
|
1718
|
+
// Depending on whether we're using pushState or hashes, and whether
|
|
1719
|
+
// 'onhashchange' is supported, determine how we check the URL state.
|
|
1720
|
+
if (this._usePushState) {
|
|
1721
|
+
addEventListener('popstate', this.checkUrl, false);
|
|
1722
|
+
} else if (this._useHashChange && !this.iframe) {
|
|
1723
|
+
addEventListener('hashchange', this.checkUrl, false);
|
|
1724
|
+
} else if (this._wantsHashChange) {
|
|
1725
|
+
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1445
1728
|
if (!this.options.silent) return this.loadUrl();
|
|
1446
1729
|
},
|
|
1447
1730
|
|
|
1448
1731
|
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
|
|
1449
1732
|
// but possibly useful for unit testing Routers.
|
|
1450
1733
|
stop: function() {
|
|
1451
|
-
|
|
1734
|
+
// Add a cross-platform `removeEventListener` shim for older browsers.
|
|
1735
|
+
var removeEventListener = window.removeEventListener || function(eventName, listener) {
|
|
1736
|
+
return detachEvent('on' + eventName, listener);
|
|
1737
|
+
};
|
|
1738
|
+
|
|
1739
|
+
// Remove window listeners.
|
|
1740
|
+
if (this._usePushState) {
|
|
1741
|
+
removeEventListener('popstate', this.checkUrl, false);
|
|
1742
|
+
} else if (this._useHashChange && !this.iframe) {
|
|
1743
|
+
removeEventListener('hashchange', this.checkUrl, false);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// Clean up the iframe if necessary.
|
|
1747
|
+
if (this.iframe) {
|
|
1748
|
+
document.body.removeChild(this.iframe);
|
|
1749
|
+
this.iframe = null;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
// Some environments will throw when clearing an undefined interval.
|
|
1452
1753
|
if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
|
|
1453
1754
|
History.started = false;
|
|
1454
1755
|
},
|
|
@@ -1463,9 +1764,13 @@
|
|
|
1463
1764
|
// calls `loadUrl`, normalizing across the hidden iframe.
|
|
1464
1765
|
checkUrl: function(e) {
|
|
1465
1766
|
var current = this.getFragment();
|
|
1767
|
+
|
|
1768
|
+
// If the user pressed the back button, the iframe's hash will have
|
|
1769
|
+
// changed and we should use that for comparison.
|
|
1466
1770
|
if (current === this.fragment && this.iframe) {
|
|
1467
|
-
current = this.
|
|
1771
|
+
current = this.getHash(this.iframe.contentWindow);
|
|
1468
1772
|
}
|
|
1773
|
+
|
|
1469
1774
|
if (current === this.fragment) return false;
|
|
1470
1775
|
if (this.iframe) this.navigate(current);
|
|
1471
1776
|
this.loadUrl();
|
|
@@ -1475,8 +1780,10 @@
|
|
|
1475
1780
|
// match, returns `true`. If no defined routes matches the fragment,
|
|
1476
1781
|
// returns `false`.
|
|
1477
1782
|
loadUrl: function(fragment) {
|
|
1783
|
+
// If the root doesn't match, no routes can match either.
|
|
1784
|
+
if (!this.matchRoot()) return false;
|
|
1478
1785
|
fragment = this.fragment = this.getFragment(fragment);
|
|
1479
|
-
return _.
|
|
1786
|
+
return _.some(this.handlers, function(handler) {
|
|
1480
1787
|
if (handler.route.test(fragment)) {
|
|
1481
1788
|
handler.callback(fragment);
|
|
1482
1789
|
return true;
|
|
@@ -1495,31 +1802,42 @@
|
|
|
1495
1802
|
if (!History.started) return false;
|
|
1496
1803
|
if (!options || options === true) options = {trigger: !!options};
|
|
1497
1804
|
|
|
1498
|
-
|
|
1805
|
+
// Normalize the fragment.
|
|
1806
|
+
fragment = this.getFragment(fragment || '');
|
|
1807
|
+
|
|
1808
|
+
// Don't include a trailing slash on the root.
|
|
1809
|
+
var rootPath = this.root;
|
|
1810
|
+
if (fragment === '' || fragment.charAt(0) === '?') {
|
|
1811
|
+
rootPath = rootPath.slice(0, -1) || '/';
|
|
1812
|
+
}
|
|
1813
|
+
var url = rootPath + fragment;
|
|
1499
1814
|
|
|
1500
|
-
// Strip the hash for matching.
|
|
1501
|
-
fragment = fragment.replace(pathStripper, '');
|
|
1815
|
+
// Strip the hash and decode for matching.
|
|
1816
|
+
fragment = this.decodeFragment(fragment.replace(pathStripper, ''));
|
|
1502
1817
|
|
|
1503
1818
|
if (this.fragment === fragment) return;
|
|
1504
1819
|
this.fragment = fragment;
|
|
1505
1820
|
|
|
1506
|
-
// Don't include a trailing slash on the root.
|
|
1507
|
-
if (fragment === '' && url !== '/') url = url.slice(0, -1);
|
|
1508
|
-
|
|
1509
1821
|
// If pushState is available, we use it to set the fragment as a real URL.
|
|
1510
|
-
if (this.
|
|
1822
|
+
if (this._usePushState) {
|
|
1511
1823
|
this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
|
|
1512
1824
|
|
|
1513
1825
|
// If hash changes haven't been explicitly disabled, update the hash
|
|
1514
1826
|
// fragment to store history.
|
|
1515
1827
|
} else if (this._wantsHashChange) {
|
|
1516
1828
|
this._updateHash(this.location, fragment, options.replace);
|
|
1517
|
-
if (this.iframe &&
|
|
1829
|
+
if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) {
|
|
1830
|
+
var iWindow = this.iframe.contentWindow;
|
|
1831
|
+
|
|
1518
1832
|
// Opening and closing the iframe tricks IE7 and earlier to push a
|
|
1519
1833
|
// history entry on hash-tag change. When replace is true, we don't
|
|
1520
1834
|
// want this.
|
|
1521
|
-
if(!options.replace)
|
|
1522
|
-
|
|
1835
|
+
if (!options.replace) {
|
|
1836
|
+
iWindow.document.open();
|
|
1837
|
+
iWindow.document.close();
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
this._updateHash(iWindow.location, fragment, options.replace);
|
|
1523
1841
|
}
|
|
1524
1842
|
|
|
1525
1843
|
// If you've told us that you explicitly don't want fallback hashchange-
|
|
@@ -1550,7 +1868,7 @@
|
|
|
1550
1868
|
// Helpers
|
|
1551
1869
|
// -------
|
|
1552
1870
|
|
|
1553
|
-
// Helper function to correctly set up the prototype chain
|
|
1871
|
+
// Helper function to correctly set up the prototype chain for subclasses.
|
|
1554
1872
|
// Similar to `goog.inherits`, but uses a hash of prototype properties and
|
|
1555
1873
|
// class properties to be extended.
|
|
1556
1874
|
var extend = function(protoProps, staticProps) {
|
|
@@ -1559,7 +1877,7 @@
|
|
|
1559
1877
|
|
|
1560
1878
|
// The constructor function for the new subclass is either defined by you
|
|
1561
1879
|
// (the "constructor" property in your `extend` definition), or defaulted
|
|
1562
|
-
// by us to simply call the parent
|
|
1880
|
+
// by us to simply call the parent constructor.
|
|
1563
1881
|
if (protoProps && _.has(protoProps, 'constructor')) {
|
|
1564
1882
|
child = protoProps.constructor;
|
|
1565
1883
|
} else {
|
|
@@ -1570,14 +1888,9 @@
|
|
|
1570
1888
|
_.extend(child, parent, staticProps);
|
|
1571
1889
|
|
|
1572
1890
|
// Set the prototype chain to inherit from `parent`, without calling
|
|
1573
|
-
// `parent`'s constructor function.
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
child.prototype = new Surrogate;
|
|
1577
|
-
|
|
1578
|
-
// Add prototype properties (instance properties) to the subclass,
|
|
1579
|
-
// if supplied.
|
|
1580
|
-
if (protoProps) _.extend(child.prototype, protoProps);
|
|
1891
|
+
// `parent`'s constructor function and add the prototype properties.
|
|
1892
|
+
child.prototype = _.create(parent.prototype, protoProps);
|
|
1893
|
+
child.prototype.constructor = child;
|
|
1581
1894
|
|
|
1582
1895
|
// Set a convenience property in case the parent's prototype is needed
|
|
1583
1896
|
// later.
|
|
@@ -1598,11 +1911,10 @@
|
|
|
1598
1911
|
var wrapError = function(model, options) {
|
|
1599
1912
|
var error = options.error;
|
|
1600
1913
|
options.error = function(resp) {
|
|
1601
|
-
if (error) error(model, resp, options);
|
|
1914
|
+
if (error) error.call(options.context, model, resp, options);
|
|
1602
1915
|
model.trigger('error', model, resp, options);
|
|
1603
1916
|
};
|
|
1604
1917
|
};
|
|
1605
1918
|
|
|
1606
1919
|
return Backbone;
|
|
1607
|
-
|
|
1608
|
-
}));
|
|
1920
|
+
});
|