This repository has been archived by the owner on May 28, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
media-query-service.js
172 lines (153 loc) · 4.95 KB
/
media-query-service.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/*!
* Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved.
*/
import angular from 'angular';
/* @ngInject */
export default function factory($rootScope, $window) {
var service = {};
// registered queries and their private listeners
service._queries = {};
// current screen state
service._state = {};
// user-registered change listeners
service._listeners = {};
/**
* Registers a listener for changes to the user's media (i.e. their screen).
*
* @param [queryNames] a set of named media queries to use to monitor changes
* to the screen, e.g. ['phone', 'desktop'], where the names
* must be registered via `registerQuery` and whose status may be
* checked using the event given to the listener via
* `event.changes[name]` or `event.isMedia(name)`.
* @param listener(event) the screen change event listener to use.
*
* @return a function that can be called to unregister the listener.
*/
service.onMediaChange = function(queryNames, listener) {
ensureFeature('matchMedia');
if(typeof queryNames === 'function') {
listener = queryNames;
queryNames = Object.keys(service._defaultScreens);
}
if(typeof queryNames === 'string') {
queryNames = [queryNames];
} else if(!angular.isArray(queryNames)) {
throw new TypeError('"queryNames" must be a string or an array.');
}
// register listener
queryNames = queryNames.slice();
angular.forEach(queryNames, function(name) {
if(!('name' in service._listeners)) {
service._listeners[name] = [];
}
service._listeners[name].push(listener);
});
// return function to unregister listener
return function unregister() {
angular.forEach(queryNames, function(name) {
var listeners = service._listeners[name];
var idx = listeners.indexOf(listener);
if(idx !== -1) {
listeners.splice(idx, 1);
}
});
};
};
/**
* Registers a named media query to allow change listeners to watch and
* check the state of the user's screen with respect to the query.
*
* @param name the name to register for the media query.
* @param media the media query itself.
*
*/
service.registerQuery = function(name, media) {
ensureFeature('matchMedia');
// unregister existing query
service.unregisterQuery(name);
// TODO: could optimize further by comparing `media` against existing
// registered queries and create an aliases for it instead of a new
// listener function
// register new query
var mediaQueryList = $window.matchMedia(media);
var listener = function(event) {
service._state = {name: event.matches};
mediaChange(name, event.media, event.matches);
};
mediaQueryList.addListener(listener);
service._queries[name] = {
name: name,
media: media,
unregister: function unregister() {
mediaQueryList.removeListener(listener);
delete service._state[name];
}
};
};
/**
* Unregisters a named media query.
*
* @param name the name of the media query to unregister.
*/
service.unregisterQuery = function(name) {
if(name in service._queries) {
service._queries[name].unregister();
}
};
/**
* Checks the status of a named media query to see if the user's media
* currently matches.
*
* @param name the name of the query to check.
*
* @return true if the query presently matches, false if not.
*/
service.isMedia = function(name) {
if(!(name in service._queries)) {
throw new Error(
'No media query has been registered with the name "' + name + '".');
}
if(!(name in service._state)) {
// do immediate query to initialize state
service._state[name] = $window.matchMedia(
service._queries[name].media).matches;
}
return service._state[name];
};
function mediaChange(name, media, matches) {
// emit change event to all listeners that are watching `name`
var listeners = service._listeners[name];
if(listeners) {
angular.forEach(listeners, function(listener) {
var event = {
queryName: name,
matches: matches,
media: media,
changes: {name: matches},
isMedia: service.isMedia
};
$rootScope.$apply(listener.bind(listener, event));
});
}
}
function ensureFeature(feature) {
if(feature in $window) {
return true;
}
throw new Error(
'The feature "' + feature + '" is not supported in this browser.');
}
// register default queries
service._defaultQueries = {
print: 'print',
phone: '(max-width: 767px)',
tablet: '(min-width: 768px) and (max-width: 979px)',
desktop: '(min-width: 979px)',
portrait: '(orientation: portrait)',
landscape: '(orientation: landscape)'
};
angular.forEach(service._defaultQueries, function(media, name) {
service.registerQuery(name, media);
});
return service;
}