UNPKG

8.93 kBJavaScriptView Raw
1/*
2 * @namespace Util
3 *
4 * Various utility functions, used by Leaflet internally.
5 */
6
7export var freeze = Object.freeze;
8Object.freeze = function (obj) { return obj; };
9
10// @function extend(dest: Object, src?: Object): Object
11// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
12export function extend(dest) {
13 var i, j, len, src;
14
15 for (j = 1, len = arguments.length; j < len; j++) {
16 src = arguments[j];
17 for (i in src) {
18 dest[i] = src[i];
19 }
20 }
21 return dest;
22}
23
24// @function create(proto: Object, properties?: Object): Object
25// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
26export var create = Object.create || (function () {
27 function F() {}
28 return function (proto) {
29 F.prototype = proto;
30 return new F();
31 };
32})();
33
34// @function bind(fn: Function, …): Function
35// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
36// Has a `L.bind()` shortcut.
37export function bind(fn, obj) {
38 var slice = Array.prototype.slice;
39
40 if (fn.bind) {
41 return fn.bind.apply(fn, slice.call(arguments, 1));
42 }
43
44 var args = slice.call(arguments, 2);
45
46 return function () {
47 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
48 };
49}
50
51// @property lastId: Number
52// Last unique ID used by [`stamp()`](#util-stamp)
53export var lastId = 0;
54
55// @function stamp(obj: Object): Number
56// Returns the unique ID of an object, assigning it one if it doesn't have it.
57export function stamp(obj) {
58 /*eslint-disable */
59 obj._leaflet_id = obj._leaflet_id || ++lastId;
60 return obj._leaflet_id;
61 /* eslint-enable */
62}
63
64// @function throttle(fn: Function, time: Number, context: Object): Function
65// Returns a function which executes function `fn` with the given scope `context`
66// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
67// `fn` will be called no more than one time per given amount of `time`. The arguments
68// received by the bound function will be any arguments passed when binding the
69// function, followed by any arguments passed when invoking the bound function.
70// Has an `L.throttle` shortcut.
71export function throttle(fn, time, context) {
72 var lock, args, wrapperFn, later;
73
74 later = function () {
75 // reset lock and call if queued
76 lock = false;
77 if (args) {
78 wrapperFn.apply(context, args);
79 args = false;
80 }
81 };
82
83 wrapperFn = function () {
84 if (lock) {
85 // called too soon, queue to call later
86 args = arguments;
87
88 } else {
89 // call and lock until later
90 fn.apply(context, arguments);
91 setTimeout(later, time);
92 lock = true;
93 }
94 };
95
96 return wrapperFn;
97}
98
99// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
100// Returns the number `num` modulo `range` in such a way so it lies within
101// `range[0]` and `range[1]`. The returned value will be always smaller than
102// `range[1]` unless `includeMax` is set to `true`.
103export function wrapNum(x, range, includeMax) {
104 var max = range[1],
105 min = range[0],
106 d = max - min;
107 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
108}
109
110// @function falseFn(): Function
111// Returns a function which always returns `false`.
112export function falseFn() { return false; }
113
114// @function formatNum(num: Number, digits?: Number): Number
115// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
116export function formatNum(num, digits) {
117 digits = (digits === undefined ? 6 : digits);
118 return +(Math.round(num + ('e+' + digits)) + ('e-' + digits));
119}
120
121// @function trim(str: String): String
122// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
123export function trim(str) {
124 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
125}
126
127// @function splitWords(str: String): String[]
128// Trims and splits the string on whitespace and returns the array of parts.
129export function splitWords(str) {
130 return trim(str).split(/\s+/);
131}
132
133// @function setOptions(obj: Object, options: Object): Object
134// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
135export function setOptions(obj, options) {
136 if (!obj.hasOwnProperty('options')) {
137 obj.options = obj.options ? create(obj.options) : {};
138 }
139 for (var i in options) {
140 obj.options[i] = options[i];
141 }
142 return obj.options;
143}
144
145// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
146// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
147// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
148// be appended at the end. If `uppercase` is `true`, the parameter names will
149// be uppercased (e.g. `'?A=foo&B=bar'`)
150export function getParamString(obj, existingUrl, uppercase) {
151 var params = [];
152 for (var i in obj) {
153 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
154 }
155 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
156}
157
158var templateRe = /\{ *([\w_-]+) *\}/g;
159
160// @function template(str: String, data: Object): String
161// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
162// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
163// `('Hello foo, bar')`. You can also specify functions instead of strings for
164// data values — they will be evaluated passing `data` as an argument.
165export function template(str, data) {
166 return str.replace(templateRe, function (str, key) {
167 var value = data[key];
168
169 if (value === undefined) {
170 throw new Error('No value provided for variable ' + str);
171
172 } else if (typeof value === 'function') {
173 value = value(data);
174 }
175 return value;
176 });
177}
178
179// @function isArray(obj): Boolean
180// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
181export var isArray = Array.isArray || function (obj) {
182 return (Object.prototype.toString.call(obj) === '[object Array]');
183};
184
185// @function indexOf(array: Array, el: Object): Number
186// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
187export function indexOf(array, el) {
188 for (var i = 0; i < array.length; i++) {
189 if (array[i] === el) { return i; }
190 }
191 return -1;
192}
193
194// @property emptyImageUrl: String
195// Data URI string containing a base64-encoded empty GIF image.
196// Used as a hack to free memory from unused images on WebKit-powered
197// mobile devices (by setting image `src` to this string).
198export var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
199
200// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
201
202function getPrefixed(name) {
203 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
204}
205
206var lastTime = 0;
207
208// fallback for IE 7-8
209function timeoutDefer(fn) {
210 var time = +new Date(),
211 timeToCall = Math.max(0, 16 - (time - lastTime));
212
213 lastTime = time + timeToCall;
214 return window.setTimeout(fn, timeToCall);
215}
216
217export var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
218export var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
219 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
220
221// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
222// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
223// `context` if given. When `immediate` is set, `fn` is called immediately if
224// the browser doesn't have native support for
225// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
226// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
227export function requestAnimFrame(fn, context, immediate) {
228 if (immediate && requestFn === timeoutDefer) {
229 fn.call(context);
230 } else {
231 return requestFn.call(window, bind(fn, context));
232 }
233}
234
235// @function cancelAnimFrame(id: Number): undefined
236// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
237export function cancelAnimFrame(id) {
238 if (id) {
239 cancelFn.call(window, id);
240 }
241}