diff --git a/src/core/util/media.js b/src/core/util/media.js index 743d2fa59b4..0033a0d6985 100644 --- a/src/core/util/media.js +++ b/src/core/util/media.js @@ -1,5 +1,5 @@ angular.module('material.core') - .factory('$mdMedia', mdMediaFactory); +.factory('$mdMedia', mdMediaFactory); /** * Exposes a function on the '$mdMedia' service which will return true or false, @@ -10,42 +10,48 @@ angular.module('material.core') * @example $mdMedia('(min-width: 1200px)') == true if device-width >= 1200px * @example $mdMedia('max-width: 300px') == true if device-width <= 300px (sanitizes input, adding parens) */ -function mdMediaFactory($window, $mdUtil, $timeout, $mdConstant) { - var cache = $mdUtil.cacheFactory('$mdMedia', { capacity: 15 }); +function mdMediaFactory($mdConstant, $mdUtil, $rootScope, $window) { + var queriesCache = $mdUtil.cacheFactory('$mdMedia:queries', {capacity: 15}); + var resultsCache = $mdUtil.cacheFactory('$mdMedia:results', {capacity: 15}); - angular.element($window).on('resize', updateAll); + angular.element($window).on('resize', updateAll); - return $mdMedia; + return $mdMedia; - function $mdMedia(query) { - query = validate(query); - var result; - if (!angular.isDefined(result = cache.get(query)) ) { - return add(query); - } - return result; + function $mdMedia(query) { + var validated = queriesCache.get(query); + if (angular.isUndefined(validated)) { + validated = queriesCache.put(query, validate(query)); } - function validate(query) { - return $mdConstant.MEDIA[query] || ( - query.charAt(0) != '(' ? ('(' + query + ')') : query - ); + var result = resultsCache.get(validated); + if (angular.isUndefined(result)) { + result = add(validated); } - function add(query) { - return cache.put(query, !!$window.matchMedia(query).matches); + return result; + } - } + function validate(query) { + return $mdConstant.MEDIA[query] || + ((query.charAt(0) !== '(') ? ('(' + query + ')') : query); + } - function updateAll() { - var keys = cache.keys(); - if (keys.length) { - for (var i = 0, ii = keys.length; i < ii; i++) { - cache.put(keys[i], !!$window.matchMedia(keys[i]).matches); - } - // trigger a $digest() - $timeout(angular.noop); - } - } + function add(query) { + return resultsCache.put(query, !!$window.matchMedia(query).matches); + } + + function updateAll() { + var keys = resultsCache.keys(); + var len = keys.length; + if (len) { + for (var i = 0; i < len; i++) { + add(keys[i]); + } + + // Trigger a $digest() if not already in progress + $rootScope.$evalAsync(); + } + } } diff --git a/src/core/util/media.spec.js b/src/core/util/media.spec.js index d1672580172..41346b431f7 100644 --- a/src/core/util/media.spec.js +++ b/src/core/util/media.spec.js @@ -1,25 +1,81 @@ describe('$mdMedia', function() { + var matchMediaResult; + var queriesCache; + var resultsCache; + beforeEach(module('material.core')); - var matchMediaResult = false; - beforeEach(inject(function($window) { + beforeEach(inject(function($cacheFactory, $mdMedia, $window) { + matchMediaResult = false; + + queriesCache = $cacheFactory.get('$mdMedia:queries'); + resultsCache = $cacheFactory.get('$mdMedia:results'); + spyOn($window, 'matchMedia').andCallFake(function() { - return { matches: matchMediaResult }; + return {matches: matchMediaResult}; }); })); - it('should validate input', inject(function($window, $mdMedia) { + afterEach(function() { + queriesCache.removeAll(); + resultsCache.removeAll(); + }); + + + it('should look up queries in `$mdConstant.MEDIA`', inject( + function($mdConstant, $mdMedia, $window) { + $mdConstant.MEDIA.somePreset = 'someQuery'; + + $mdMedia('somePreset'); + expect($window.matchMedia).toHaveBeenCalledWith('someQuery'); + + delete($mdConstant.MEDIA.somePreset); + } + )); + + it('should look up validated queries in `queriesCache`', inject(function($mdMedia, $window) { + queriesCache.put('originalQuery', 'validatedQuery'); + + $mdMedia('originalQuery'); + expect($window.matchMedia).toHaveBeenCalledWith('validatedQuery'); + })); + + it('should validate queries', inject(function($mdMedia, $window) { $mdMedia('something'); expect($window.matchMedia).toHaveBeenCalledWith('(something)'); })); - it('should return result of matchMedia and recalculate on resize', inject(function($window, $mdMedia) { + it('should cache validated queries in `queriesCache`', inject(function($mdMedia) { + $mdMedia('query'); + expect(queriesCache.get('query')).toBe('(query)'); + })); + + it('should return cached results if available', inject(function($mdMedia) { + resultsCache.put('(query)', 'result'); + expect($mdMedia('(query)')).toBe('result'); + })); + + it('should cache results in `resultsCache`', inject(function($mdMedia) { + $mdMedia('(query)'); + expect(resultsCache.get('(query)')).toBe(false); + })); + + it('should recalculate on resize', inject(function($mdMedia, $window) { matchMediaResult = true; - expect($mdMedia('foo')).toBe(true); + expect($mdMedia('query')).toBe(true); + expect($window.matchMedia.callCount).toBe(1); + + expect($mdMedia('query')).toBe(true); + expect($window.matchMedia.callCount).toBe(1); + matchMediaResult = false; - expect($mdMedia('foo')).toBe(true); + expect($mdMedia('query')).toBe(true); + expect($window.matchMedia.callCount).toBe(1); + angular.element($window).triggerHandler('resize'); - expect($mdMedia('foo')).toBe(false); + + expect($mdMedia('query')).toBe(false); + expect($window.matchMedia.callCount).toBe(2); })); }); diff --git a/src/core/util/util.js b/src/core/util/util.js index aa5d70e62cc..3922633d787 100644 --- a/src/core/util/util.js +++ b/src/core/util/util.js @@ -266,19 +266,32 @@ angular.module('material.core') */ function cacheFactory(id, options) { var cache = $cacheFactory(id, options); - var keys = {}; + cache._put = cache.put; cache.put = function(k,v) { keys[k] = true; return cache._put(k, v); }; + cache._remove = cache.remove; cache.remove = function(k) { delete keys[k]; return cache._remove(k); }; + cache._removeAll = cache.removeAll; + cache.removeAll = function() { + keys = {}; + return cache._removeAll(); + }; + + cache._destroy = cache.destroy; + cache.destroy = function() { + keys = null; + return cache._destroy(); + }; + cache.keys = function() { return Object.keys(keys); };