diff --git a/app/assets/javascripts/darkswarm/controllers/checkout/accordion_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout/accordion_controller.js.coffee index d28437dd21..a80a899f17 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout/accordion_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout/accordion_controller.js.coffee @@ -1,11 +1,11 @@ -Darkswarm.controller "AccordionCtrl", ($scope, storage, $timeout, $document, CurrentHub) -> - $scope.accordion = - details: true +Darkswarm.controller "AccordionCtrl", ($scope, localStorageService, $timeout, $document, CurrentHub) -> + $scope.accordion = + details: true billing: false shipping: false payment: false $scope.accordionSections = ["details", "billing", "shipping", "payment"] - storage.bind $scope, "accordion", {storeName: "accordion_#{$scope.order.id}#{CurrentHub.hub.id}#{$scope.order.user_id}"} + localStorageService.bind $scope, "accordion", $scope.accordion, "accordion_#{$scope.order.id}#{CurrentHub.hub.id}#{$scope.order.user_id}" $scope.show = (section)-> $scope.accordion[section] = true diff --git a/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee index d9ec0ea985..8a4f187773 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.controller "CheckoutCtrl", ($scope, storage, Checkout, CurrentUser, CurrentHub) -> +Darkswarm.controller "CheckoutCtrl", ($scope, localStorageService, Checkout, CurrentUser, CurrentHub) -> $scope.Checkout = Checkout $scope.submitted = false @@ -7,20 +7,11 @@ Darkswarm.controller "CheckoutCtrl", ($scope, storage, Checkout, CurrentUser, Cu prefix = "order_#{Checkout.order.id}#{CurrentUser.id or ""}#{CurrentHub.hub.id}" for field in $scope.fieldsToBind - storage.bind $scope, "Checkout.order.#{field}", - storeName: "#{prefix}_#{field}" + localStorageService.bind $scope, "Checkout.order.#{field}", Checkout.order[field], "#{prefix}_#{field}" - storage.bind $scope, "Checkout.ship_address_same_as_billing", - storeName: "#{prefix}_sameasbilling" - defaultValue: true - - storage.bind $scope, "Checkout.default_bill_address", - storeName: "#{prefix}_defaultasbilladdress" - defaultValue: false - - storage.bind $scope, "Checkout.default_ship_address", - storeName: "#{prefix}_defaultasshipaddress" - defaultValue: false + localStorageService.bind $scope, "Checkout.ship_address_same_as_billing", true, "#{prefix}_sameasbilling" + localStorageService.bind $scope, "Checkout.default_bill_address", false, "#{prefix}_defaultasbilladdress" + localStorageService.bind $scope, "Checkout.default_ship_address", false, "#{prefix}_defaultasshipaddress" $scope.order = Checkout.order # Ordering is important $scope.secrets = Checkout.secrets diff --git a/app/assets/javascripts/darkswarm/darkswarm.js.coffee b/app/assets/javascripts/darkswarm/darkswarm.js.coffee index ce5a73d69e..2b51bc309c 100644 --- a/app/assets/javascripts/darkswarm/darkswarm.js.coffee +++ b/app/assets/javascripts/darkswarm/darkswarm.js.coffee @@ -1,6 +1,6 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource", 'mm.foundation', - 'angularLocalStorage', + 'LocalStorageModule', 'infinite-scroll', 'angular-flash.service', 'templates', diff --git a/app/assets/javascripts/darkswarm/directives/change_order_cycle.js.coffee b/app/assets/javascripts/darkswarm/directives/change_order_cycle.js.coffee index b2478a6fad..574c29cc30 100644 --- a/app/assets/javascripts/darkswarm/directives/change_order_cycle.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/change_order_cycle.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.directive "ofnChangeOrderCycle", (OrderCycle, Cart, storage) -> +Darkswarm.directive "ofnChangeOrderCycle", (OrderCycle, Cart) -> # Compares chosen order cycle with pre-set OrderCycle. Will trigger # a confirmation if they are different, and Cart isn't empty restrict: "A" diff --git a/app/assets/javascripts/darkswarm/services/cart.js.coffee b/app/assets/javascripts/darkswarm/services/cart.js.coffee index 456d567bb7..70cbe316e8 100644 --- a/app/assets/javascripts/darkswarm/services/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/services/cart.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, storage)-> +Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, localStorageService)-> # Handles syncing of current cart/order state to server new class Cart dirty: false @@ -114,4 +114,4 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo clear: -> @line_items = [] - storage.clearAll() # One day this will have to be moar GRANULAR + localStorageService.clearAll() # One day this will have to be moar GRANULAR diff --git a/app/assets/javascripts/darkswarm/services/checkout.js.coffee b/app/assets/javascripts/darkswarm/services/checkout.js.coffee index a4225f4eff..8f43b783a2 100644 --- a/app/assets/javascripts/darkswarm/services/checkout.js.coffee +++ b/app/assets/javascripts/darkswarm/services/checkout.js.coffee @@ -3,9 +3,6 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h errors: {} secrets: {} order: CurrentOrder.order - ship_address_same_as_billing: true - default_bill_address: false - default_ship_address: false submit: -> Loading.message = t 'submitting_order' @@ -22,8 +19,8 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h # Rails wants our Spree::Address data to be provided with _attributes preprocess: -> munged_order = - default_bill_address: @default_bill_address - default_ship_address: @default_ship_address + default_bill_address: !!@default_bill_address + default_ship_address: !!@default_ship_address for name, value of @order # Clone all data from the order JSON object switch name @@ -38,7 +35,7 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h else # Ignore everything else - if @ship_address_same_as_billing == 'YES' + if @ship_address_same_as_billing munged_order.ship_address_attributes = munged_order.bill_address_attributes # If the order already has a ship and bill address (as with logged in users with # past orders), and we don't remove id here, then this will set the wrong id for diff --git a/app/assets/javascripts/shared/angular-local-storage.js b/app/assets/javascripts/shared/angular-local-storage.js index 2ac18eba9e..dfde2f6cfb 100644 --- a/app/assets/javascripts/shared/angular-local-storage.js +++ b/app/assets/javascripts/shared/angular-local-storage.js @@ -1,171 +1,546 @@ -/* - * Angular.js localStorage module - * https://github.com/agrublev/angularLocalStorage +/** + * An Angular module that gives you access to the browsers local storage + * @version v0.5.0 - 2016-08-29 + * @link https://github.com/grevory/angular-local-storage + * @author grevory + * @license MIT License, http://www.opensource.org/licenses/MIT */ +(function (window, angular) { +var isDefined = angular.isDefined, + isUndefined = angular.isUndefined, + isNumber = angular.isNumber, + isObject = angular.isObject, + isArray = angular.isArray, + extend = angular.extend, + toJson = angular.toJson; -(function (window, angular, undefined) { - 'use strict'; +angular + .module('LocalStorageModule', []) + .provider('localStorageService', function() { + // You should set a prefix to avoid overwriting any local storage variables from the rest of your app + // e.g. localStorageServiceProvider.setPrefix('yourAppName'); + // With provider you can use config as this: + // myApp.config(function (localStorageServiceProvider) { + // localStorageServiceProvider.prefix = 'yourAppName'; + // }); + this.prefix = 'ls'; - 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'; + // You could change web storage type localstorage or sessionStorage + this.storageType = 'localStorage'; - 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; - } - }; + // Cookie options (usually in case of fallback) + // expiry = Number of days before cookies expire // 0 = Does not expire + // path = The web path the cookie represents + // secure = Wether the cookies should be secure (i.e only sent on HTTPS requests) + this.cookie = { + expiry: 30, + path: '/', + secure: false + }; - 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); - }, + // Decides wether we should default to cookies if localstorage is not supported. + this.defaultToCookie = true; - /** - * 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); - }, + // Send signals for each of the following actions? + this.notify = { + setItem: true, + removeItem: false + }; - /** - * 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; - }, + // Setter for the prefix + this.setPrefix = function(prefix) { + this.prefix = prefix; + return this; + }; - /** - * 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); - } + // Setter for the storageType + this.setStorageType = function(storageType) { + this.storageType = storageType; + return this; + }; + // Setter for defaultToCookie value, default is true. + this.setDefaultToCookie = function (shouldDefault) { + this.defaultToCookie = !!shouldDefault; // Double-not to make sure it's a bool value. + return this; + }; + // Setter for cookie config + this.setStorageCookie = function(exp, path, secure) { + this.cookie.expiry = exp; + this.cookie.path = path; + this.cookie.secure = secure; + return this; + }; - // Set the storeName key for the localStorage entry - // use user defined in specified - var storeName = opts.storeName || key; + // Setter for cookie domain + this.setStorageCookieDomain = function(domain) { + this.cookie.domain = domain; + return this; + }; - // If a value doesn't already exist store it as is - if (!publicMethods.get(storeName)) { - publicMethods.set(storeName, $parse(key)($scope) || opts.defaultValue); - } else { - // If it does exist assign it to the $scope value - $parse(key).assign($scope, publicMethods.get(storeName)); + // Setter for notification config + // itemSet & itemRemove should be booleans + this.setNotify = function(itemSet, itemRemove) { + this.notify = { + setItem: itemSet, + removeItem: itemRemove + }; + return this; + }; + + this.$get = ['$rootScope', '$window', '$document', '$parse','$timeout', function($rootScope, $window, $document, $parse, $timeout) { + var self = this; + var prefix = self.prefix; + var cookie = self.cookie; + var notify = self.notify; + var storageType = self.storageType; + var webStorage; + + // When Angular's $document is not available + if (!$document) { + $document = document; + } else if ($document[0]) { + $document = $document[0]; + } + + // If there is a prefix set in the config lets use that with an appended period for readability + if (prefix.substr(-1) !== '.') { + prefix = !!prefix ? prefix + '.' : ''; + } + var deriveQualifiedKey = function(key) { + return prefix + key; + }; + + // Removes prefix from the key. + var underiveQualifiedKey = function (key) { + return key.replace(new RegExp('^' + prefix, 'g'), ''); + }; + + // Check if the key is within our prefix namespace. + var isKeyPrefixOurs = function (key) { + return key.indexOf(prefix) === 0; + }; + + // Checks the browser to see if local storage is supported + var checkSupport = function () { + try { + var supported = (storageType in $window && $window[storageType] !== null); + + // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage + // is available, but trying to call .setItem throws an exception. + // + // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage + // that exceeded the quota." + var key = deriveQualifiedKey('__' + Math.round(Math.random() * 1e7)); + if (supported) { + webStorage = $window[storageType]; + webStorage.setItem(key, ''); + webStorage.removeItem(key); + } + + return supported; + } catch (e) { + // Only change storageType to cookies if defaulting is enabled. + if (self.defaultToCookie) + storageType = 'cookie'; + $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); + return false; + } + }; + var browserSupportsLocalStorage = checkSupport(); + + // Directly adds a value to local storage + // If local storage is not available in the browser use cookies + // Example use: localStorageService.add('library','angular'); + var addToLocalStorage = function (key, value, type) { + setStorageType(type); + + // Let's convert undefined values to null to get the value consistent + if (isUndefined(value)) { + value = null; + } else { + value = toJson(value); } + // If this browser does not support local storage use cookies + if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { + if (!browserSupportsLocalStorage) { + $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); + } - // 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); + if (notify.setItem) { + $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: 'cookie'}); + } + return addToCookies(key, value); + } - 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(); - } - }; + try { + if (webStorage) { + webStorage.setItem(deriveQualifiedKey(key), value); + } + if (notify.setItem) { + $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType}); + } + } catch (e) { + $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); + return addToCookies(key, value); + } + return true; + }; - return publicMethods; - }]); + // Directly get a value from local storage + // Example use: localStorageService.get('library'); // returns 'angular' + var getFromLocalStorage = function (key, type) { + setStorageType(type); + if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { + if (!browserSupportsLocalStorage) { + $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); + } + + return getFromCookies(key); + } + + var item = webStorage ? webStorage.getItem(deriveQualifiedKey(key)) : null; + // angular.toJson will convert null to 'null', so a proper conversion is needed + // FIXME not a perfect solution, since a valid 'null' string can't be stored + if (!item || item === 'null') { + return null; + } + + try { + return JSON.parse(item); + } catch (e) { + return item; + } + }; + + // Remove an item from local storage + // Example use: localStorageService.remove('library'); // removes the key/value pair of library='angular' + // + // This is var-arg removal, check the last argument to see if it is a storageType + // and set type accordingly before removing. + // + var removeFromLocalStorage = function () { + // can't pop on arguments, so we do this + var consumed = 0; + if (arguments.length >= 1 && + (arguments[arguments.length - 1] === 'localStorage' || + arguments[arguments.length - 1] === 'sessionStorage')) { + consumed = 1; + setStorageType(arguments[arguments.length - 1]); + } + + var i, key; + for (i = 0; i < arguments.length - consumed; i++) { + key = arguments[i]; + if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { + if (!browserSupportsLocalStorage) { + $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); + } + + if (notify.removeItem) { + $rootScope.$broadcast('LocalStorageModule.notification.removeitem', {key: key, storageType: 'cookie'}); + } + removeFromCookies(key); + } + else { + try { + webStorage.removeItem(deriveQualifiedKey(key)); + if (notify.removeItem) { + $rootScope.$broadcast('LocalStorageModule.notification.removeitem', { + key: key, + storageType: self.storageType + }); + } + } catch (e) { + $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); + removeFromCookies(key); + } + } + } + }; + + // Return array of keys for local storage + // Example use: var keys = localStorageService.keys() + var getKeysForLocalStorage = function (type) { + setStorageType(type); + + if (!browserSupportsLocalStorage) { + $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); + return []; + } + + var prefixLength = prefix.length; + var keys = []; + for (var key in webStorage) { + // Only return keys that are for this app + if (key.substr(0, prefixLength) === prefix) { + try { + keys.push(key.substr(prefixLength)); + } catch (e) { + $rootScope.$broadcast('LocalStorageModule.notification.error', e.Description); + return []; + } + } + } + return keys; + }; + + // Remove all data for this app from local storage + // Also optionally takes a regular expression string and removes the matching key-value pairs + // Example use: localStorageService.clearAll(); + // Should be used mostly for development purposes + var clearAllFromLocalStorage = function (regularExpression, type) { + setStorageType(type); + + // Setting both regular expressions independently + // Empty strings result in catchall RegExp + var prefixRegex = !!prefix ? new RegExp('^' + prefix) : new RegExp(); + var testRegex = !!regularExpression ? new RegExp(regularExpression) : new RegExp(); + + if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { + if (!browserSupportsLocalStorage) { + $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); + } + return clearAllFromCookies(); + } + if (!browserSupportsLocalStorage && !self.defaultToCookie) + return false; + var prefixLength = prefix.length; + + for (var key in webStorage) { + // Only remove items that are for this app and match the regular expression + if (prefixRegex.test(key) && testRegex.test(key.substr(prefixLength))) { + try { + removeFromLocalStorage(key.substr(prefixLength)); + } catch (e) { + $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); + return clearAllFromCookies(); + } + } + } + return true; + }; + + // Checks the browser to see if cookies are supported + var browserSupportsCookies = (function() { + try { + return $window.navigator.cookieEnabled || + ("cookie" in $document && ($document.cookie.length > 0 || + ($document.cookie = "test").indexOf.call($document.cookie, "test") > -1)); + } catch (e) { + $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); + return false; + } + }()); + + // Directly adds a value to cookies + // Typically used as a fallback if local storage is not available in the browser + // Example use: localStorageService.cookie.add('library','angular'); + var addToCookies = function (key, value, daysToExpiry, secure) { + + if (isUndefined(value)) { + return false; + } else if(isArray(value) || isObject(value)) { + value = toJson(value); + } + + if (!browserSupportsCookies) { + $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED'); + return false; + } + + try { + var expiry = '', + expiryDate = new Date(), + cookieDomain = ''; + + if (value === null) { + // Mark that the cookie has expired one day ago + expiryDate.setTime(expiryDate.getTime() + (-1 * 24 * 60 * 60 * 1000)); + expiry = "; expires=" + expiryDate.toGMTString(); + value = ''; + } else if (isNumber(daysToExpiry) && daysToExpiry !== 0) { + expiryDate.setTime(expiryDate.getTime() + (daysToExpiry * 24 * 60 * 60 * 1000)); + expiry = "; expires=" + expiryDate.toGMTString(); + } else if (cookie.expiry !== 0) { + expiryDate.setTime(expiryDate.getTime() + (cookie.expiry * 24 * 60 * 60 * 1000)); + expiry = "; expires=" + expiryDate.toGMTString(); + } + if (!!key) { + var cookiePath = "; path=" + cookie.path; + if (cookie.domain) { + cookieDomain = "; domain=" + cookie.domain; + } + /* Providing the secure parameter always takes precedence over config + * (allows developer to mix and match secure + non-secure) */ + if (typeof secure === 'boolean') { + if (secure === true) { + /* We've explicitly specified secure, + * add the secure attribute to the cookie (after domain) */ + cookieDomain += "; secure"; + } + // else - secure has been supplied but isn't true - so don't set secure flag, regardless of what config says + } + else if (cookie.secure === true) { + // secure parameter wasn't specified, get default from config + cookieDomain += "; secure"; + } + $document.cookie = deriveQualifiedKey(key) + "=" + encodeURIComponent(value) + expiry + cookiePath + cookieDomain; + } + } catch (e) { + $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); + return false; + } + return true; + }; + + // Directly get a value from a cookie + // Example use: localStorageService.cookie.get('library'); // returns 'angular' + var getFromCookies = function (key) { + if (!browserSupportsCookies) { + $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED'); + return false; + } + + var cookies = $document.cookie && $document.cookie.split(';') || []; + for(var i=0; i < cookies.length; i++) { + var thisCookie = cookies[i]; + while (thisCookie.charAt(0) === ' ') { + thisCookie = thisCookie.substring(1,thisCookie.length); + } + if (thisCookie.indexOf(deriveQualifiedKey(key) + '=') === 0) { + var storedValues = decodeURIComponent(thisCookie.substring(prefix.length + key.length + 1, thisCookie.length)); + try { + return JSON.parse(storedValues); + } catch(e) { + return storedValues; + } + } + } + return null; + }; + + var removeFromCookies = function (key) { + addToCookies(key,null); + }; + + var clearAllFromCookies = function () { + var thisCookie = null; + var prefixLength = prefix.length; + var cookies = $document.cookie.split(';'); + for(var i = 0; i < cookies.length; i++) { + thisCookie = cookies[i]; + + while (thisCookie.charAt(0) === ' ') { + thisCookie = thisCookie.substring(1, thisCookie.length); + } + + var key = thisCookie.substring(prefixLength, thisCookie.indexOf('=')); + removeFromCookies(key); + } + }; + + var getStorageType = function() { + return storageType; + }; + + var setStorageType = function(type) { + if (type && storageType !== type) { + storageType = type; + browserSupportsLocalStorage = checkSupport(); + } + return browserSupportsLocalStorage; + }; + + // Add a listener on scope variable to save its changes to local storage + // Return a function which when called cancels binding + var bindToScope = function(scope, key, def, lsKey, type) { + lsKey = lsKey || key; + var value = getFromLocalStorage(lsKey, type); + + if (value === null && isDefined(def)) { + value = def; + } else if (isObject(value) && isObject(def)) { + value = extend(value, def); + } + + $parse(key).assign(scope, value); + + return scope.$watch(key, function(newVal) { + addToLocalStorage(lsKey, newVal, type); + }, isObject(scope[key])); + }; + + // Add listener to local storage, for update callbacks. + if (browserSupportsLocalStorage) { + if ($window.addEventListener) { + $window.addEventListener("storage", handleStorageChangeCallback, false); + $rootScope.$on('$destroy', function() { + $window.removeEventListener("storage", handleStorageChangeCallback); + }); + } else if($window.attachEvent){ + // attachEvent and detachEvent are proprietary to IE v6-10 + $window.attachEvent("onstorage", handleStorageChangeCallback); + $rootScope.$on('$destroy', function() { + $window.detachEvent("onstorage", handleStorageChangeCallback); + }); + } + } + + // Callback handler for storage changed. + function handleStorageChangeCallback(e) { + if (!e) { e = $window.event; } + if (notify.setItem) { + if (isKeyPrefixOurs(e.key)) { + var key = underiveQualifiedKey(e.key); + // Use timeout, to avoid using $rootScope.$apply. + $timeout(function () { + $rootScope.$broadcast('LocalStorageModule.notification.changed', { key: key, newvalue: e.newValue, storageType: self.storageType }); + }); + } + } + } + + // Return localStorageService.length + // ignore keys that not owned + var lengthOfLocalStorage = function(type) { + setStorageType(type); + + var count = 0; + var storage = $window[storageType]; + for(var i = 0; i < storage.length; i++) { + if(storage.key(i).indexOf(prefix) === 0 ) { + count++; + } + } + return count; + }; + + return { + isSupported: browserSupportsLocalStorage, + getStorageType: getStorageType, + setStorageType: setStorageType, + set: addToLocalStorage, + add: addToLocalStorage, //DEPRECATED + get: getFromLocalStorage, + keys: getKeysForLocalStorage, + remove: removeFromLocalStorage, + clearAll: clearAllFromLocalStorage, + bind: bindToScope, + deriveKey: deriveQualifiedKey, + underiveKey: underiveQualifiedKey, + length: lengthOfLocalStorage, + defaultToCookie: this.defaultToCookie, + cookie: { + isSupported: browserSupportsCookies, + set: addToCookies, + add: addToCookies, //DEPRECATED + get: getFromCookies, + remove: removeFromCookies, + clearAll: clearAllFromCookies + } + }; + }]; + }); })(window, window.angular); diff --git a/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee index 3f5546649b..f3ad508c4d 100644 --- a/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee @@ -6,7 +6,7 @@ describe "CheckoutCtrl", -> CurrentHubMock = hub: id: 1 - storage = null + localStorageService = null beforeEach -> module("Darkswarm") @@ -23,14 +23,16 @@ describe "CheckoutCtrl", -> id: 1 email: "public" user_id: 1 + bill_address: 'bill_address' + ship_address: 'ship address' secrets: card_number: "this is a secret" describe "with user", -> beforeEach -> - inject ($controller, $rootScope, _storage_) -> - storage = _storage_ - spyOn(storage, "bind").and.callThrough() + inject ($controller, $rootScope, _localStorageService_) -> + localStorageService = _localStorageService_ + spyOn(localStorageService, "bind").and.callThrough() scope = $rootScope.$new() CurrentUser = { id: 1 } ctrl = $controller 'CheckoutCtrl', {$scope: scope, Checkout: Checkout, CurrentUser: CurrentUser } @@ -57,23 +59,24 @@ describe "CheckoutCtrl", -> expect(scope.enabled).toEqual true describe "Local storage", -> - it "binds to localStorage when given a scope", -> + it "binds to localStorage when given a scope", inject ($timeout) -> prefix = "order_#{scope.order.id}#{CurrentUser.id or ""}#{CurrentHubMock.hub.id}" field = scope.fieldsToBind[0] - expect(storage.bind).toHaveBeenCalledWith(scope, "Checkout.order.#{field}", {storeName: "#{prefix}_#{field}"}) - expect(storage.bind).toHaveBeenCalledWith(scope, "Checkout.ship_address_same_as_billing", {storeName: "#{prefix}_sameasbilling", defaultValue: true}) - expect(storage.bind).toHaveBeenCalledWith(scope, "Checkout.default_bill_address", {storeName: "#{prefix}_defaultasbilladdress", defaultValue: false}) - expect(storage.bind).toHaveBeenCalledWith(scope, "Checkout.default_ship_address", {storeName: "#{prefix}_defaultasshipaddress", defaultValue: false}) - + expect(localStorageService.bind).toHaveBeenCalledWith(scope, "Checkout.order.#{field}", Checkout.order[field], "#{prefix}_#{field}") + expect(localStorageService.bind).toHaveBeenCalledWith(scope, "Checkout.ship_address_same_as_billing", true, "#{prefix}_sameasbilling") + expect(localStorageService.bind).toHaveBeenCalledWith(scope, "Checkout.default_bill_address", false, "#{prefix}_defaultasbilladdress") + expect(localStorageService.bind).toHaveBeenCalledWith(scope, "Checkout.default_ship_address", false, "#{prefix}_defaultasshipaddress") it "it can retrieve data from localstorage", -> prefix = "order_#{scope.order.id}#{CurrentUser.id or ""}#{CurrentHubMock.hub.id}" - expect(localStorage.getItem("#{prefix}_email")).toMatch "public" + scope.$digest() + expect(localStorage.getItem("ls.#{prefix}_email")).toMatch "public" it "does not store secrets in local storage", -> Checkout.secrets = card_number: "superfuckingsecret" + scope.$digest() keys = (localStorage.key(i) for i in [0..localStorage.length]) for key in keys expect(localStorage.getItem(key)).not.toMatch Checkout.secrets.card_number @@ -88,6 +91,9 @@ describe "CheckoutCtrl", -> expect(scope.enabled).toEqual false it "does not store secrets in local storage", -> + Checkout.secrets = + card_number: "superfuckingsecret" + scope.$digest() keys = (localStorage.key(i) for i in [0..localStorage.length]) for key in keys expect(localStorage.getItem(key)).not.toMatch Checkout.secrets.card_number