UNPKG

18.4 kBJavaScriptView Raw
1/*!
2 * OpenUI5
3 * (c) Copyright 2009-2022 SAP SE or an SAP affiliate company.
4 * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
5 */
6// Provides miscellaneous utility functions that might be useful for any script
7sap.ui.define([
8 'jquery.sap.global',
9 'sap/base/util/uid',
10 'sap/base/strings/hash',
11 'sap/base/util/array/uniqueSort',
12 'sap/base/util/deepEqual',
13 'sap/base/util/each',
14 'sap/base/util/array/diff',
15 'sap/base/util/JSTokenizer',
16 'sap/base/util/merge',
17 'sap/base/util/UriParameters'
18], function(jQuery, uid, hash, uniqueSort, deepEqual, each, diff, JSTokenizer, merge, UriParameters) {
19 "use strict";
20
21 /**
22 * Creates and returns a pseudo-unique id.
23 *
24 * No means for detection of overlap with already present or future UIDs.
25 *
26 * @return {string} A pseudo-unique id.
27 * @public
28 * @function
29 * @deprecated since 1.58 use {@link module:sap/base/util/uid} instead
30 */
31 jQuery.sap.uid = uid;
32
33 /**
34 * This function generates a hash-code from a string
35 * @param {string} sString The string to generate the hash-code from
36 * @return {int} The generated hash-code
37 * @since 1.39
38 * @private
39 * @ui5-restricted sap.ui.core
40 * @function
41 * @deprecated since 1.58 use {@link module:sap/base/strings/hash} instead
42 */
43 jQuery.sap.hashCode = hash;
44
45
46 /**
47 * Sorts the given array in-place and removes any duplicates (identified by "===").
48 *
49 * Use <code>jQuery.uniqueSort()</code> for arrays of DOMElements.
50 *
51 * @param {Array} a An Array of any type
52 * @return {Array} Same array as given (for chaining)
53 * @public
54 * @function
55 * @deprecated since 1.58 use {@link module:sap/base/util/array/uniqueSort} instead
56 */
57 jQuery.sap.unique = uniqueSort;
58
59 /**
60 * Compares the two given values for equality, especially takes care not to compare
61 * arrays and objects by reference, but compares their content.
62 * Note: function does not work with comparing XML objects
63 *
64 * @param {any} a A value of any type
65 * @param {any} b A value of any type
66 * @param {int} [maxDepth=10] Maximum recursion depth
67 * @param {boolean} [contains] Whether all existing properties in a are equal as in b
68 *
69 * @return {boolean} Whether a and b are equal
70 * @public
71 * @function
72 * @deprecated since 1.58 use {@link module:sap/base/util/deepEqual} instead
73 */
74 jQuery.sap.equal = deepEqual;
75
76 /**
77 * Iterates over elements of the given object or array.
78 *
79 * Works similar to <code>jQuery.each</code>, but a numeric index is only used for
80 * instances of <code>Array</code>. For all other objects, including those with a numeric
81 * <code>length</code> property, the properties are iterated by name.
82 *
83 * The contract for the <code>fnCallback</code> is the same as for <code>jQuery.each</code>,
84 * when it returns <code>false</code>, then the iteration stops (break).
85 *
86 * @param {object|any[]} oObject object or array to enumerate the properties of
87 * @param {function} fnCallback function to call for each property name
88 * @return {object|any[]} the given <code>oObject</code>
89 * @since 1.11
90 * @function
91 * @deprecated since 1.58 use {@link module:sap/base/util/each} instead
92 */
93 jQuery.sap.each = each;
94
95 /**
96 * Calculate delta of old list and new list.
97 *
98 * This function implements the algorithm described in "A Technique for Isolating Differences Between Files"
99 * (Commun. ACM, April 1978, Volume 21, Number 4, Pages 264-268).
100 *
101 * Items in the arrays are not compared directly. Instead, a substitute symbol is determined for each item
102 * by applying the provided function <code>fnSymbol</code> to it. Items with strictly equal symbols are
103 * assumed to represent the same logical item:
104 * <pre>
105 * fnSymbol(a) === fnSymbol(b) <=> a 'is logically the same as' b
106 * </pre>
107 * As an additional constraint, casting the symbols to string should not modify the comparison result.
108 * If this second constraint is not met, this method might report more diffs than necessary.
109 *
110 * If no symbol function is provided, a default implementation is used which applies <code>JSON.stringify</code>
111 * to non-string items and reduces the strings to a hash code. It is not guaranteed that this default
112 * implementation fulfills the above constraint in all cases, but it is a compromise between implementation
113 * effort, generality and performance. If items are known to be non-stringifiable (e.g. because they may
114 * contain cyclic references) or when hash collisions are likely, an own <code>fnSymbol</code> function
115 * must be provided.
116 *
117 * The result of the diff is a sequence of update operations, each consisting of a <code>type</code>
118 * (either <code>"insert"</code> or <code>"delete"</code>) and an <code>index</code>.
119 * By applying the operations one after the other to the old array, it can be transformed to an
120 * array whose items are equal to the new array.
121 *
122 * Sample implementation of the update
123 * <pre>
124 *
125 * function update(aOldArray, aNewArray) {
126 *
127 * // calculate the diff
128 * var aDiff = jQuery.sap.arraySymbolDiff(aOldArray, aNewArray, __provide_your_symbol_function_here__);
129 *
130 * // apply update operations
131 * aDiff.forEach( function(op) {
132 *
133 * // invariant: aOldArray and aNewArray now are equal up to (excluding) op.index
134 *
135 * switch ( op.type ) {
136 * case 'insert':
137 * // new array contains a new (or otherwise unmapped) item, add it here
138 * aOldArray.splice(op.index, 0, aNewArray[op.index]);
139 * break;
140 * case 'delete':
141 * // an item is no longer part of the array (or has been moved to another position), remove it
142 * aOldArray.splice(op.index, 1);
143 * break;
144 * default:
145 * throw new Error('unexpected diff operation type');
146 * }
147 *
148 * });
149 * }
150 *
151 * </pre>
152 *
153 * @param {Array} aOld Old Array
154 * @param {Array} aNew New Array
155 * @param {function} [fnSymbol] Function to calculate substitute symbols for array items
156 * @return {Array.<{type:string,index:int}>} List of update operations
157 * @public
158 * @function
159 * @deprecated since 1.58 use {@link module:sap/base/util/array/diff} instead
160 */
161 jQuery.sap.arraySymbolDiff = diff;
162
163
164 /**
165 * A factory returning a tokenizer object for JS values.
166 * Contains functions to consume tokens on an input string.
167 * @function
168 * @private
169 * @returns {object} - the tokenizer
170 * @deprecated since 1.58 use {@link module:sap/base/util/JSTokenizer} instead
171 */
172 jQuery.sap._createJSTokenizer = function() {
173 return new JSTokenizer();
174 };
175
176 /**
177 * Parse simple JS objects.
178 *
179 * A parser for JS object literals. This is different from a JSON parser, as it does not have
180 * the JSON specification as a format description, but a subset of the JavaScript language.
181 * The main difference is, that keys in objects do not need to be quoted and strings can also
182 * be defined using apostrophes instead of quotation marks.
183 *
184 * The parser does not support functions, but only boolean, number, string, object and array.
185 *
186 * @function
187 * @param {string} The string containing the JS objects
188 * @throws an error, if the string does not contain a valid JS object
189 * @returns {object} the JS object
190 *
191 * @private
192 * @since 1.11
193 * @deprecated since 1.58 use {@link module:sap/base/util/JSTokenizer.parseJS} instead
194 */
195 jQuery.sap.parseJS = JSTokenizer.parseJS;
196
197 /**
198 * Merge the contents of two or more objects together into the first object.
199 * Usage is the same as jQuery.extend, but Arguments that are null or undefined are NOT ignored.
200 *
201 * @deprecated since 1.58. For shallow extend use <code>Object.assign</code> (polyfilled), for deep extend use <code>sap/base/util/merge</code>.
202 * @function
203 * @since 1.26
204 * @private
205 */
206 jQuery.sap.extend = function () {
207 var args = arguments,
208 deep = false;
209
210 // Check whether the first argument is the deep-flag
211 if (typeof arguments[0] === "boolean") {
212 deep = arguments[0];
213
214 // skip the first argument while creating a shallow copy of arguments
215 args = Array.prototype.slice.call(arguments, 1);
216 }
217
218 if (deep) {
219 return merge.apply(this, args);
220 } else {
221 /*
222 * The code in this function is taken from jQuery 3.6.0 "jQuery.extend" and got modified.
223 *
224 * jQuery JavaScript Library v3.6.0
225 * https://jquery.com/
226 *
227 * Copyright OpenJS Foundation and other contributors
228 * Released under the MIT license
229 * https://jquery.org/license
230 */
231 var copy, name, options,
232 target = arguments[0] || {},
233 i = 1,
234 length = arguments.length;
235
236 // Handle case when target is a string or something (possible in deep copy)
237 if (typeof target !== "object" && typeof target !== "function") {
238 target = {};
239 }
240
241 for (; i < length; i++) {
242
243 options = arguments[i];
244
245 // Extend the base object
246 for (name in options) {
247 copy = options[name];
248
249 // Prevent never-ending loop
250 if (name === "__proto__" || target === copy) {
251 continue;
252 }
253
254 target[name] = copy;
255 }
256 }
257
258 // Return the modified object
259 return target;
260 }
261 };
262
263 // Javadoc for private inner class "UriParams" - this list of comments is intentional!
264 /**
265 * @interface Encapsulates all URI parameters of the current windows location (URL).
266 *
267 * Use {@link jQuery.sap.getUriParameters} to create an instance of jQuery.sap.util.UriParameters.
268 *
269 * @author SAP SE
270 * @version 1.90.11
271 * @since 0.9.0
272 * @name jQuery.sap.util.UriParameters
273 * @public
274 */
275
276 /**
277 * Returns the value(s) of the URI parameter with the given name sName.
278 *
279 * If the boolean parameter bAll is <code>true</code>, an array of string values of all
280 * occurrences of the URI parameter with the given name is returned. This array is empty
281 * if the URI parameter is not contained in the windows URL.
282 *
283 * If the boolean parameter bAll is <code>false</code> or is not specified, the value of the first
284 * occurrence of the URI parameter with the given name is returned. Might be <code>null</code>
285 * if the URI parameter is not contained in the windows URL.
286 *
287 * @public
288 * @param {string} sUri The name of the URI parameter.
289 * @return {string|array} The value(s) of the URI parameter with the given name
290 * @SecSource {return|XSS} Return value contains URL parameters
291 * @function
292 * @name jQuery.sap.util.UriParameters.prototype.get
293 */
294
295 /**
296 * Creates and returns a new instance of {@link jQuery.sap.util.UriParameters}.
297 *
298 * Example for reading a single URI parameter (or the value of the first
299 * occurrence of the URI parameter):
300 * <pre>
301 * var sValue = jQuery.sap.getUriParameters().get("myUriParam");
302 * </pre>
303 *
304 * Example for reading the values of the first of the URI parameter
305 * (with multiple occurrences):
306 * <pre>
307 * var aValues = jQuery.sap.getUriParameters().get("myUriParam", true);
308 * for(i in aValues){
309 * var sValue = aValues[i];
310 * }
311 * </pre>
312 *
313 * @public
314 * @param {string} sUri Uri to determine the parameters for
315 * @return {jQuery.sap.util.UriParameters} A new URI parameters instance
316 * @deprecated As of 1.68, use {@link module:sap/base/util/UriParameters.fromQuery UriParameters.fromQuery}
317 * or {@link module:sap/base/util/UriParameters.fromURL UriParameters.fromURL} instead.
318 */
319 jQuery.sap.getUriParameters = function getUriParameters(sUri) {
320 return UriParameters.fromURL(sUri || window.location.href);
321 };
322
323 /**
324 * Calls a method after a given delay and returns an id for this timer
325 *
326 * @param {int} iDelay Delay time in milliseconds
327 * @param {object} oObject Object from which the method should be called
328 * @param {string|object} method function pointer or name of the method
329 * @param {array} [aParameters] Method parameters
330 * @return {string} Id which can be used to cancel the timer with clearDelayedCall
331 * @public
332 * @deprecated since 1.58 use native <code>setTimeout</code> instead
333 */
334 jQuery.sap.delayedCall = function delayedCall(iDelay, oObject, method, aParameters) {
335 return setTimeout(function(){
336 if (typeof method === "string") {
337 method = oObject[method];
338 }
339 method.apply(oObject, aParameters || []);
340 }, iDelay);
341 };
342
343 /**
344 * Stops the delayed call.
345 *
346 * The function given when calling delayedCall is not called anymore.
347 *
348 * @param {string} sDelayedCallId The id returned, when calling delayedCall
349 * @public
350 * @deprecated since 1.58 use native <code>clearTimeout</code> instead
351 */
352 jQuery.sap.clearDelayedCall = function clearDelayedCall(sDelayedCallId) {
353 clearTimeout(sDelayedCallId);
354 return this;
355 };
356
357 /**
358 * Calls a method after a given interval and returns an id for this interval.
359 *
360 * @param {int} iInterval Interval time in milliseconds
361 * @param {object} oObject Object from which the method should be called
362 * @param {string|object} method function pointer or name of the method
363 * @param {array} [aParameters] Method parameters
364 * @return {string} Id which can be used to cancel the interval with clearIntervalCall
365 * @public
366 * @deprecated since 1.58 use native <code>setInterval</code> instead
367 */
368 jQuery.sap.intervalCall = function intervalCall(iInterval, oObject, method, aParameters) {
369 return setInterval(function(){
370 if (typeof method === "string") {
371 method = oObject[method];
372 }
373 method.apply(oObject, aParameters || []);
374 }, iInterval);
375 };
376
377 /**
378 * Stops the interval call.
379 *
380 * The function given when calling intervalCall is not called anymore.
381 *
382 * @param {string} sIntervalCallId The id returned, when calling intervalCall
383 * @public
384 * @deprecated since 1.58 use native <code>clearInterval</code> instead
385 */
386 jQuery.sap.clearIntervalCall = function clearIntervalCall(sIntervalCallId) {
387 clearInterval(sIntervalCallId);
388 return this;
389 };
390
391 /**
392 * Substitute for <code>for(n in o)</code> loops.
393 * This function is just a wrapper around the native for-in loop.
394 *
395 * Iterates over all enumerable properties of the given object and calls the
396 * given callback function for each of them. The assumed signature of the
397 * callback function is
398 *
399 * fnCallback(name, value)
400 *
401 * where name is the name of the property and value is its value.
402 *
403 * @param {object} oObject object to enumerate the properties of
404 * @param {function} fnCallback function to call for each property name
405 * @deprecated since 1.48.0. Use native for-in loop instead.
406 * @since 1.7.1
407 */
408 jQuery.sap.forIn = each;
409
410 /**
411 * Calculate delta of old list and new list.
412 *
413 * This partly implements the algorithm described in "A Technique for Isolating Differences Between Files"
414 * but instead of working with hashes, it does compare each entry of the old list with each entry of the new
415 * list, which causes terrible performance on large datasets.
416 *
417 * @deprecated As of 1.38, use {@link module:sap/base/util/array/diff} instead if applicable
418 * @public
419 * @param {Array} aOld Old Array
420 * @param {Array} aNew New Array
421 * @param {function} [fnCompare] Function to compare list entries
422 * @param {boolean} [bUniqueEntries] Whether entries are unique, so no duplicate entries exist
423 * @return {Array} List of changes
424 */
425 jQuery.sap.arrayDiff = function(aOld, aNew, fnCompare, bUniqueEntries){
426 fnCompare = fnCompare || function(vValue1, vValue2) {
427 return deepEqual(vValue1, vValue2);
428 };
429
430 var aOldRefs = [];
431 var aNewRefs = [];
432
433 //Find references
434 var aMatches = [];
435 for (var i = 0; i < aNew.length; i++) {
436 var oNewEntry = aNew[i];
437 var iFound = 0;
438 var iTempJ;
439 // if entries are unique, first check for whether same index is same entry
440 // and stop searching as soon the first matching entry is found
441 if (bUniqueEntries && fnCompare(aOld[i], oNewEntry)) {
442 iFound = 1;
443 iTempJ = i;
444 } else {
445 for (var j = 0; j < aOld.length; j++) {
446 if (fnCompare(aOld[j], oNewEntry)) {
447 iFound++;
448 iTempJ = j;
449 if (bUniqueEntries || iFound > 1) {
450 break;
451 }
452 }
453 }
454 }
455 if (iFound == 1) {
456 var oMatchDetails = {
457 oldIndex: iTempJ,
458 newIndex: i
459 };
460 if (aMatches[iTempJ]) {
461 delete aOldRefs[iTempJ];
462 delete aNewRefs[aMatches[iTempJ].newIndex];
463 } else {
464 aNewRefs[i] = {
465 data: aNew[i],
466 row: iTempJ
467 };
468 aOldRefs[iTempJ] = {
469 data: aOld[iTempJ],
470 row: i
471 };
472 aMatches[iTempJ] = oMatchDetails;
473 }
474 }
475 }
476
477 //Pass 4: Find adjacent matches in ascending order
478 for (var i = 0; i < aNew.length - 1; i++) {
479 if (aNewRefs[i] &&
480 !aNewRefs[i + 1] &&
481 aNewRefs[i].row + 1 < aOld.length &&
482 !aOldRefs[aNewRefs[i].row + 1] &&
483 fnCompare(aOld[ aNewRefs[i].row + 1 ], aNew[i + 1])) {
484
485 aNewRefs[i + 1] = {
486 data: aNew[i + 1],
487 row: aNewRefs[i].row + 1
488 };
489 aOldRefs[aNewRefs[i].row + 1] = {
490 data: aOldRefs[aNewRefs[i].row + 1],
491 row: i + 1
492 };
493
494 }
495 }
496
497 //Pass 5: Find adjacent matches in descending order
498 for (var i = aNew.length - 1; i > 0; i--) {
499 if (aNewRefs[i] &&
500 !aNewRefs[i - 1] &&
501 aNewRefs[i].row > 0 &&
502 !aOldRefs[aNewRefs[i].row - 1] &&
503 fnCompare(aOld[aNewRefs[i].row - 1], aNew[i - 1])) {
504
505 aNewRefs[i - 1] = {
506 data: aNew[i - 1],
507 row: aNewRefs[i].row - 1
508 };
509 aOldRefs[aNewRefs[i].row - 1] = {
510 data: aOldRefs[aNewRefs[i].row - 1],
511 row: i - 1
512 };
513
514 }
515 }
516
517 //Pass 6: Generate diff data
518 var aDiff = [];
519
520 if (aNew.length == 0) {
521 //New list is empty, all items were deleted
522 for (var i = 0; i < aOld.length; i++) {
523 aDiff.push({
524 index: 0,
525 type: 'delete'
526 });
527 }
528 } else {
529 var iNewListIndex = 0;
530 if (!aOldRefs[0]) {
531 //Detect all deletions at the beginning of the old list
532 for (var i = 0; i < aOld.length && !aOldRefs[i]; i++) {
533 aDiff.push({
534 index: 0,
535 type: 'delete'
536 });
537 iNewListIndex = i + 1;
538 }
539 }
540
541 for (var i = 0; i < aNew.length; i++) {
542 if (!aNewRefs[i] || aNewRefs[i].row > iNewListIndex) {
543 //Entry doesn't exist in old list = insert
544 aDiff.push({
545 index: i,
546 type: 'insert'
547 });
548 } else {
549 iNewListIndex = aNewRefs[i].row + 1;
550 for (var j = aNewRefs[i].row + 1; j < aOld.length && (!aOldRefs[j] || aOldRefs[j].row < i); j++) {
551 aDiff.push({
552 index: i + 1,
553 type: 'delete'
554 });
555 iNewListIndex = j + 1;
556 }
557 }
558 }
559 }
560
561 return aDiff;
562 };
563
564 return jQuery;
565});