diff --git a/app/assets/javascripts/darkswarm/all.js.coffee b/app/assets/javascripts/darkswarm/all.js.coffee index ea8c47eed8..7f09206ea3 100644 --- a/app/assets/javascripts/darkswarm/all.js.coffee +++ b/app/assets/javascripts/darkswarm/all.js.coffee @@ -4,8 +4,10 @@ #= require spin # #= require angular +#= require angular-cookies #= require angular-resource #= require ../shared/mm-foundation-tpls-0.2.0-SNAPSHOT +#= require ../shared/angular-local-storage.js # #= require ../shared/jquery.timeago #= require foundation diff --git a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee index f1cd687fa8..538255f74c 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee @@ -1,9 +1,12 @@ -Darkswarm.controller "CheckoutCtrl", ($scope, $rootScope, Order) -> - $scope.require_ship_address = false +Darkswarm.controller "CheckoutCtrl", ($scope, $rootScope, Order, storage) -> $scope.order = $scope.Order = Order - $scope.shippingMethodChanged = -> - Order.shippingMethodChanged() + # Binding accordion panel states to local storage + storage.bind $scope, "user" + storage.bind $scope, "details" + storage.bind $scope, "billing" + storage.bind $scope, "shipping" + storage.bind $scope, "payment" $scope.purchase = (event)-> event.preventDefault() diff --git a/app/assets/javascripts/darkswarm/darkswarm.js.coffee b/app/assets/javascripts/darkswarm/darkswarm.js.coffee index 6be3a4a155..f50ffcf9ef 100644 --- a/app/assets/javascripts/darkswarm/darkswarm.js.coffee +++ b/app/assets/javascripts/darkswarm/darkswarm.js.coffee @@ -1,4 +1,4 @@ -window.Darkswarm = angular.module("Darkswarm", ["ngResource", "filters", 'mm.foundation']).config ($httpProvider, $tooltipProvider) -> +window.Darkswarm = angular.module("Darkswarm", ["ngResource", "filters", 'mm.foundation', 'angularLocalStorage']).config ($httpProvider, $tooltipProvider) -> $httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content') $httpProvider.defaults.headers['common']['X-Requested-With'] = 'XMLHttpRequest' $httpProvider.defaults.headers.common.Accept = "application/json, text/javascript, */*" diff --git a/app/assets/javascripts/darkswarm/services/order.js.coffee b/app/assets/javascripts/darkswarm/services/order.js.coffee index 98d8b8fa42..10c2d5322f 100644 --- a/app/assets/javascripts/darkswarm/services/order.js.coffee +++ b/app/assets/javascripts/darkswarm/services/order.js.coffee @@ -1,24 +1,17 @@ Darkswarm.factory 'Order', ($resource, Product, order)-> - - ## I am being clever here - ## order is a JSON object generated in shop/checkout/order.rabl - ## We're extending this to add methods while retaining the data! - new class Order constructor: -> @[name] = method for name, method of order # Clone all data from the order JSON object - # Our shipping_methods comes through as a hash like so: {id: requires_shipping_address} # Here we default to the first shipping method if none is selected - @shipping_method_id ||= Object.keys(@shipping_methods)[0] - @ship_address_same_as_billing = true if @ship_address_same_as_billing == null - @shippingMethodChanged() + @shipping_method_id ||= parseInt(Object.keys(@shipping_methods)[0]) + @ship_address_same_as_billing ?= true shippingMethod: -> @shipping_methods[@shipping_method_id] - shippingMethodChanged: => - @require_ship_address = @shippingMethod().require_ship_address if @shippingMethod() + requireShipAddress: -> + @shippingMethod().require_ship_address shippingPrice: -> @shippingMethod().price @@ -26,7 +19,6 @@ Darkswarm.factory 'Order', ($resource, Product, order)-> paymentMethod: -> @payment_methods[@payment_method_id] - cartTotal: -> @shippingPrice() + @display_total diff --git a/app/assets/javascripts/darkswarm/services/order_cycle.js.coffee b/app/assets/javascripts/darkswarm/services/order_cycle.js.coffee index c7a4473b03..1bb23446a2 100644 --- a/app/assets/javascripts/darkswarm/services/order_cycle.js.coffee +++ b/app/assets/javascripts/darkswarm/services/order_cycle.js.coffee @@ -1,14 +1,13 @@ Darkswarm.factory 'OrderCycle', ($resource, Product, orderCycleData) -> class OrderCycle - @order_cycle = orderCycleData || null + @order_cycle = orderCycleData # Object or {} due to RABL @push_order_cycle: -> new $resource("/shop/order_cycle").save {order_cycle_id: @order_cycle.order_cycle_id}, (order_data)-> OrderCycle.order_cycle.orders_close_at = order_data.orders_close_at Product.update() @orders_close_at: -> - if @selected() - @order_cycle.orders_close_at + @order_cycle.orders_close_at if @selected() @selected: -> - @order_cycle != null and !$.isEmptyObject(@order_cycle) and @order_cycle.orders_close_at != undefined + !$.isEmptyObject(@order_cycle) and @order_cycle.orders_close_at? diff --git a/app/assets/javascripts/shared/angular-local-storage.js b/app/assets/javascripts/shared/angular-local-storage.js new file mode 100644 index 0000000000..dfe1e2471c --- /dev/null +++ b/app/assets/javascripts/shared/angular-local-storage.js @@ -0,0 +1,170 @@ +/* + * Angular.js localStorage module + * https://github.com/agrublev/angularLocalStorage + */ + +(function (window, angular, undefined) { + 'use strict'; + + angular.module('angularLocalStorage', ['ngCookies']).factory('storage', ['$parse', '$cookieStore', '$window', '$log', function ($parse, $cookieStore, $window, $log) { + /** + * Global Vars + */ + var storage = (typeof $window.localStorage === 'undefined') ? undefined : $window.localStorage; + var supported = typeof storage !== 'undefined'; + + var privateMethods = { + /** + * Pass any type of a string from the localStorage to be parsed so it returns a usable version (like an Object) + * @param res - a string that will be parsed for type + * @returns {*} - whatever the real type of stored value was + */ + parseValue: function (res) { + var val; + try { + val = angular.fromJson(res); + if (typeof val === 'undefined') { + val = res; + } + if (val === 'true') { + val = true; + } + if (val === 'false') { + val = false; + } + if ($window.parseFloat(val) === val && !angular.isObject(val)) { + val = $window.parseFloat(val); + } + } catch (e) { + val = res; + } + return val; + } + }; + + var publicMethods = { + /** + * Set - let's you set a new localStorage key pair set + * @param key - a string that will be used as the accessor for the pair + * @param value - the value of the localStorage item + * @returns {*} - will return whatever it is you've stored in the local storage + */ + set: function (key, value) { + if (!supported) { + try { + $cookieStore.put(key, value); + return value; + } catch(e) { + $log.log('Local Storage not supported, make sure you have angular-cookies enabled.'); + } + } + var saver = angular.toJson(value); + storage.setItem(key, saver); + return privateMethods.parseValue(saver); + }, + + /** + * Get - let's you get the value of any pair you've stored + * @param key - the string that you set as accessor for the pair + * @returns {*} - Object,String,Float,Boolean depending on what you stored + */ + get: function (key) { + if (!supported) { + try { + return privateMethods.parseValue($.cookie(key)); + } catch (e) { + return null; + } + } + var item = storage.getItem(key); + return privateMethods.parseValue(item); + }, + + /** + * Remove - let's you nuke a value from localStorage + * @param key - the accessor value + * @returns {boolean} - if everything went as planned + */ + remove: function (key) { + if (!supported) { + try { + $cookieStore.remove(key); + return true; + } catch (e) { + return false; + } + } + storage.removeItem(key); + return true; + }, + + /** + * Bind - let's you directly bind a localStorage value to a $scope variable + * @param {Angular $scope} $scope - the current scope you want the variable available in + * @param {String} key - the name of the variable you are binding + * @param {Object} opts - (optional) custom options like default value or unique store name + * Here are the available options you can set: + * * defaultValue: the default value + * * storeName: add a custom store key value instead of using the scope variable name + * @returns {*} - returns whatever the stored value is + */ + bind: function ($scope, key, opts) { + var defaultOpts = { + defaultValue: '', + storeName: '' + }; + // Backwards compatibility with old defaultValue string + if (angular.isString(opts)) { + opts = angular.extend({},defaultOpts,{defaultValue:opts}); + } else { + // If no defined options we use defaults otherwise extend defaults + opts = (angular.isUndefined(opts)) ? defaultOpts : angular.extend(defaultOpts,opts); + } + + // Set the storeName key for the localStorage entry + // use user defined in specified + var storeName = opts.storeName || key; + + // If a value doesn't already exist store it as is + if (!publicMethods.get(storeName)) { + publicMethods.set(storeName, opts.defaultValue); + } + + // If it does exist assign it to the $scope value + $parse(key).assign($scope, publicMethods.get(storeName)); + + // Register a listener for changes on the $scope value + // to update the localStorage value + $scope.$watch(key, function (val) { + if (angular.isDefined(val)) { + publicMethods.set(storeName, val); + } + }, true); + + return publicMethods.get(storeName); + }, + /** + * Unbind - let's you unbind a variable from localStorage while removing the value from both + * the localStorage and the local variable and sets it to null + * @param $scope - the scope the variable was initially set in + * @param key - the name of the variable you are unbinding + * @param storeName - (optional) if you used a custom storeName you will have to specify it here as well + */ + unbind: function($scope,key,storeName) { + storeName = storeName || key; + $parse(key).assign($scope, null); + $scope.$watch(key, function () { }); + publicMethods.remove(storeName); + }, + /** + * Clear All - let's you clear out ALL localStorage variables, use this carefully! + */ + clearAll: function() { + storage.clear(); + } + }; + + return publicMethods; + }]); + +})(window, window.angular); diff --git a/app/views/shop/checkout/_authentication.html.haml b/app/views/shop/checkout/_authentication.html.haml new file mode 100644 index 0000000000..20a160aaed --- /dev/null +++ b/app/views/shop/checkout/_authentication.html.haml @@ -0,0 +1,9 @@ +%fieldset + %accordion-group{heading: "User"} + .row + %section#checkout_login + .large-6.columns + = render partial: "shop/checkout/login" + %section#checkout_signup + .large-6.columns + = render partial: "shop/checkout/signup" diff --git a/app/views/shop/checkout/_details.html.haml b/app/views/shop/checkout/_details.html.haml index 241a65c4d6..20408d0e44 100644 --- a/app/views/shop/checkout/_details.html.haml +++ b/app/views/shop/checkout/_details.html.haml @@ -1,5 +1,5 @@ %fieldset#details - %accordion-group + %accordion-group{"is-open" => "details"} %accordion-heading .row .large-6.columns diff --git a/app/views/shop/checkout/_order.rabl b/app/views/shop/checkout/_order.rabl index 4bba881fec..48aef7b11e 100644 --- a/app/views/shop/checkout/_order.rabl +++ b/app/views/shop/checkout/_order.rabl @@ -17,7 +17,6 @@ child current_order.ship_address => :ship_address do attributes :phone, :firstname, :lastname, :address1, :address2, :city, :country_id, :state_id, :zipcode end -# Format here is {id: require_ship_address} node :shipping_methods do Hash[current_order.distributor.shipping_methods.collect { |method| [method.id, { diff --git a/app/views/shop/checkout/_shipping.html.haml b/app/views/shop/checkout/_shipping.html.haml index 5a93ca6075..62aed1d235 100644 --- a/app/views/shop/checkout/_shipping.html.haml +++ b/app/views/shop/checkout/_shipping.html.haml @@ -1,5 +1,5 @@ %fieldset#shipping - %accordion-group + %accordion-group{"is-open" => "shipping"} %accordion-heading .row .large-6.columns @@ -9,24 +9,19 @@ - for ship_method, i in current_distributor.shipping_methods.uniq .row .large-12.columns - -#= f.radio_button :shipping_method_id, ship_method.id, - -#text: ship_method.name, - -#"ng-change" => "shippingMethodChanged()", - -#"ng-model" => "order.shipping_method_id" %label = radio_button_tag "order[shipping_method_id]", ship_method.id, false, - "ng-change" => "order.shippingMethodChanged()", "ng-model" => "order.shipping_method_id" = ship_method.name - #distributor_address.panel{"ng-show" => "!order.require_ship_address"} + #distributor_address.panel{"ng-show" => "!order.requireShipAddress()"} = @order.distributor.distributor_info.andand.html_safe = @order.order_cycle.pickup_time_for(@order.distributor) = @order.order_cycle.pickup_instructions_for(@order.distributor) = f.fields_for :ship_address, @order.ship_address do |sa| - #ship_address{"ng-show" => "order.require_ship_address"} + #ship_address{"ng-show" => "order.requireShipAddress()"} %label = hidden_field_tag "order[ship_address_same_as_billing]", "false" = check_box_tag "order[ship_address_same_as_billing]", true, @order.ship_address_same_as_billing, diff --git a/app/views/shop/checkout/edit.html.haml b/app/views/shop/checkout/edit.html.haml index bca55e2a54..09b2561409 100644 --- a/app/views/shop/checkout/edit.html.haml +++ b/app/views/shop/checkout/edit.html.haml @@ -10,15 +10,7 @@ %accordion.row{"close-others" => "false"} .large-9.columns - unless spree_current_user - %fieldset - %accordion-group{heading: "User"} - .row - %section#checkout_login - .large-6.columns - = render partial: "shop/checkout/login" - %section#checkout_signup - .large-6.columns - = render partial: "shop/checkout/signup" + = render partial: "shop/checkout/authentication" .row = render partial: "shop/checkout/form" diff --git a/app/views/shop/shop/_order_cycles.html.haml b/app/views/shop/shop/_order_cycles.html.haml index d121bb5fb8..7c5ff51df3 100644 --- a/app/views/shop/shop/_order_cycles.html.haml +++ b/app/views/shop/shop/_order_cycles.html.haml @@ -14,3 +14,5 @@ - else %form.custom = yield :order_cycle_form + + diff --git a/app/views/shop/shop/show.html.haml b/app/views/shop/shop/show.html.haml index f8380c6c39..c036eec016 100644 --- a/app/views/shop/shop/show.html.haml +++ b/app/views/shop/shop/show.html.haml @@ -5,7 +5,7 @@ %select.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id", "ng-change" => "changeOrderCycle()", "ng-options" => "oc.id as oc.time for oc in #{@order_cycles.map {|oc| {time: pickup_time(oc), id: oc.id}}.to_json}", - "popover-placement" => "bottom", "popover" => "testy", "popover-trigger" => "openTrigger"} + "popover-placement" => "bottom", "popover" => "Please select an order cycle", "popover-trigger" => "openTrigger"} %closing{"ng-if" => "OrderCycle.selected()"} Orders close diff --git a/spec/features/consumer/shopping/checkout_auth_spec.rb b/spec/features/consumer/shopping/checkout_auth_spec.rb index f7c48664f8..7a770cf9ee 100644 --- a/spec/features/consumer/shopping/checkout_auth_spec.rb +++ b/spec/features/consumer/shopping/checkout_auth_spec.rb @@ -23,7 +23,6 @@ feature "As a consumer I want to check out my cart", js: true do add_product_to_cart end - context "logged in" do before do login_to_consumer_section @@ -39,7 +38,6 @@ feature "As a consumer I want to check out my cart", js: true do context "logged out" do before do visit "/shop/checkout" - save_and_open_page toggle_accordion "User" end diff --git a/spec/javascripts/unit/darkswarm/controllers/checkout_controller_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/checkout_controller_spec.js.coffee index fae814e4e8..08358a4368 100644 --- a/spec/javascripts/unit/darkswarm/controllers/checkout_controller_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/controllers/checkout_controller_spec.js.coffee @@ -5,30 +5,7 @@ describe "CheckoutCtrl", -> beforeEach -> module("Darkswarm") - order = - id: 3102 - shipping_method_id: "7" - ship_address_same_as_billing: true - payment_method_id: null - shipping_methods: - 7: - require_ship_address: true - price: 0.0 - - 25: - require_ship_address: false - price: 13 + order = {} inject ($controller) -> scope = {} - ctrl = $controller 'CheckoutCtrl', {$scope: scope, order: order} - - - it 'Gets the ship address automatically', -> - expect(scope.require_ship_address).toEqual true - - it 'Gets the current shipping price', -> - expect(scope.shippingPrice()).toEqual 0.0 - scope.order.shipping_method_id = 25 - expect(scope.shippingPrice()).toEqual 13 - - + ctrl = $controller 'CheckoutCtrl', {$scope: scope, Order: order} diff --git a/spec/javascripts/unit/darkswarm/order_cycle_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/order_cycle_spec.js.coffee similarity index 95% rename from spec/javascripts/unit/darkswarm/order_cycle_spec.js.coffee rename to spec/javascripts/unit/darkswarm/services/order_cycle_spec.js.coffee index 9687e72380..f7a65867e8 100644 --- a/spec/javascripts/unit/darkswarm/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/order_cycle_spec.js.coffee @@ -39,6 +39,6 @@ describe 'OrderCycle service', -> it "tells us when no order cycle is selected", -> OrderCycle.order_cycle = null expect(OrderCycle.selected()).toEqual false - OrderCycle.order_cycle = {test: "blah"} + OrderCycle.order_cycle = {orders_close_at: "10 days ago"} expect(OrderCycle.selected()).toEqual true diff --git a/spec/javascripts/unit/darkswarm/services/order_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/order_spec.js.coffee new file mode 100644 index 0000000000..1e0dc387ed --- /dev/null +++ b/spec/javascripts/unit/darkswarm/services/order_spec.js.coffee @@ -0,0 +1,47 @@ +describe 'Order service', -> + Order = null + orderData = null + + beforeEach -> + orderData = { + id: 3102 + payment_method_id: null + shipping_methods: + 7: + require_ship_address: true + price: 0.0 + + 25: + require_ship_address: false + price: 13 + payment_methods: + 99: + test: "foo" + } + angular.module('Darkswarm').value('order', orderData) + module 'Darkswarm' + inject ($injector)-> + Order = $injector.get("Order") + + it "defaults the shipping method to the first", -> + expect(Order.shipping_method_id).toEqual 7 + expect(Order.shippingMethod()).toEqual { require_ship_address : true, price : 0 } + + it "defaults to 'same as billing' for address", -> + expect(Order.ship_address_same_as_billing).toEqual true + + it 'Tracks whether a ship address is required', -> + expect(Order.requireShipAddress()).toEqual true + Order.shipping_method_id = 25 + expect(Order.requireShipAddress()).toEqual false + + it 'Gets the current shipping price', -> + expect(Order.shippingPrice()).toEqual 0.0 + Order.shipping_method_id = 25 + expect(Order.shippingPrice()).toEqual 13 + + it 'Gets the current payment method', -> + expect(Order.paymentMethod()).toEqual null + Order.payment_method_id = 99 + expect(Order.paymentMethod()).toEqual {test: "foo"} +