-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcore.util.ts
311 lines (285 loc) · 11.6 KB
/
core.util.ts
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/**
* Copyright 2019 Next Century Corporation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FieldConfig } from './models/dataset';
import { FilterCollection, ListFilter, ListFilterDesign } from './models/filters';
export class CoreUtil {
// eslint-disable-next-line max-len
static URL_PATTERN = /(?:(?:http:\/\/)|(?:https:\/\/)|(?:ftp:\/\/)|(?:file:\/\/)|(?:www\.).)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b[-a-zA-Z0-9@:%_+.~#?&\\/=]*/g;
/**
* Adds a listener on the given event to the given child element that dispatches a copy of the event using the given parent element.
*/
static addEventPropagationListener(parentElement: HTMLElement, childElement: HTMLElement, eventName: string): void {
childElement.addEventListener(eventName, (event: any) => {
parentElement.dispatchEvent(new CustomEvent(eventName, {
detail: event.detail
}));
});
}
/**
* Add the given listener of the given event on the element with the given ID.
*/
static addListener(listener: (event: any) => void, parentElement: HTMLElement, elementId: string, eventName: string): void {
if (elementId && eventName && parentElement) {
const element = parentElement.querySelector('#' + elementId);
if (element) {
element.addEventListener(eventName, listener);
}
}
}
/**
* Changes the given array of values to an array with the given values, or toggles the given values in the given array of values.
*/
static changeOrToggleMultipleValues(newValues: any[], oldValues: any[], toggle: boolean = false): any[] {
if (!toggle) {
oldValues.splice(0, oldValues.length);
}
newValues.forEach((newValue) => {
const index = oldValues.indexOf(newValue);
if (index < 0) {
oldValues.push(newValue);
} else {
oldValues.splice(index, 1);
}
});
return oldValues;
}
/**
* Changes the given array of values to an array with the given value, or toggles the given value in the given array of values.
*/
static changeOrToggleValues(value: any, values: any[], toggle: boolean = false): any[] {
if (toggle) {
const index = values.indexOf(value);
if (index < 0) {
values.push(value);
} else {
values.splice(index, 1);
}
} else {
values.splice(0, values.length, value);
}
return values;
}
static checkStringForUrl(text: string) {
// Need to use match operator and not RegExp.exec() because use of global flag
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
let matches = text.match(CoreUtil.URL_PATTERN);
let prefixPattern = new RegExp('^(http|https|ftp|file)://');
let temp;
matches.forEach((url, index) => {
if (!prefixPattern.test(url)) {
temp = 'http://' + url;
matches[index] = temp;
}
});
return matches;
}
/**
* Returns the object nested inside the given object using the given path string (with periods to mark each nested property).
*
* @arg {object} item
* @arg {string} pathString
* @return {object}
*/
static deepFind(item, pathString) {
if (item && typeof item[pathString] !== 'undefined') {
return item[pathString];
}
let itemToReturn = item;
let path = (pathString ? pathString.split('.') : []);
for (let index = 0; index < path.length; index++) {
if (itemToReturn instanceof Array) {
let nestedPath = path.slice(index).join('.');
let pieces = [];
for (let itemInList of itemToReturn) {
let entryValue = CoreUtil.deepFind(itemInList, nestedPath);
if (entryValue instanceof Array) {
entryValue = CoreUtil.flatten(entryValue);
}
pieces = pieces.concat(entryValue);
}
return pieces;
}
itemToReturn = itemToReturn ? itemToReturn[path[index]] : undefined;
}
return itemToReturn;
}
/**
* Flattens and returns the given array.
*
* @arg {array} input
* @return {array}
*/
static flatten(input) {
return (input || []).reduce((array, element) => array.concat(Array.isArray(element) ? CoreUtil.flatten(element) : element), []);
}
static hasUrl(text: string) {
let test = CoreUtil.URL_PATTERN.test(text);
let url = [];
let splitText = [];
if (test) {
url = CoreUtil.checkStringForUrl(text);
splitText = CoreUtil.splitStringByUrl(text);
}
return {
test,
url,
splitText
};
}
/**
* Returns true if the given item contains a value from the given Map of filtered values for all the given filter fields.
*/
static isItemFilteredInEveryField(item: any, fields: FieldConfig[], fieldsToValues: Map<string, any[]>): boolean {
return !fields.length ? false : fields.filter((field) => !!field.columnName).every((field) => {
const values: any[] = fieldsToValues.get(field.columnName) || [];
return (!values.length && !item[field.columnName]) || values.indexOf(item[field.columnName]) >= 0;
});
}
/**
* Returns whether the given item is a number.
*/
static isNumber(item: any): boolean {
return !isNaN(parseFloat(item)) && isFinite(item);
}
/**
* Returns the prettified string of the given integer (with commas).
*/
static prettifyInteger(item: number): string {
return Math.round(item).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
/**
* Removes the given listener of the given event on the element with the given ID.
*/
static removeListener(listener: (event: any) => void, parentElement: HTMLElement, elementId: string, eventName: string): void {
if (elementId && eventName && parentElement) {
const element = parentElement.querySelector('#' + elementId);
if (element) {
element.removeEventListener(eventName, listener);
}
}
}
/**
* Returns the values in the given ListFilter objects.
*/
static retrieveValuesFromListFilters(filters: ListFilter[]): any[] {
return filters.reduce((list, filter) => list.concat(filter.values), []);
}
/**
* Dynamic sorting over an array of objects
* https://www.sitepoint.com/sort-an-array-of-objects-in-javascript/
*
* @arg {array} array
* @arg {string} key
* @arg {number} [order=1] 1 if ascending or -1 if descending
* @return {array}
*/
static sortArrayOfObjects(array: any[], key: string, order: number = 1) {
return array.sort((object1, object2) => {
if (!object1.hasOwnProperty(key) || !object2.hasOwnProperty(key)) {
// Property doesn't exist on either object
return 0;
}
const varA = (typeof object1[key] === 'string') ? object1[key].toUpperCase() : object1[key];
const varB = (typeof object2[key] === 'string') ? object2[key].toUpperCase() : object2[key];
let comparison = 0;
if (varA > varB) {
comparison = 1;
} else if (varA < varB) {
comparison = -1;
}
return comparison * order;
});
}
static splitStringByUrl(text: string) {
let textParts = text.split(CoreUtil.URL_PATTERN);
return textParts;
}
/**
* Transforms the given attributes of an HTMLElement into an object.
*/
static transformElementAttributes(elementAttributes: NamedNodeMap): Record<string, any> {
let attributes: Record<string, any> = {};
// I don't think(?) we're able to use a for-of on a NamedNodeMap.
/* eslint-disable-next-line @typescript-eslint/prefer-for-of */
for (let index = 0; index < elementAttributes.length; ++index) {
attributes[elementAttributes[index].name] = elementAttributes[index].value;
}
return attributes;
}
/**
* Transforms the given string or string array into a string array and returns the array.
*
* @arg {string|string[]} input
* @return {string[]}
*/
static transformToStringArray(input, delimiter: string) {
if (Array.isArray(input)) {
return input;
}
if (input !== '' && input !== null && typeof input !== 'undefined') {
let inputValue = input.toString();
if (inputValue.indexOf('[') === 0 && inputValue.lastIndexOf(']') === (inputValue.length - 1) &&
typeof inputValue !== 'undefined') {
inputValue = inputValue.substring(1, inputValue.length - 1);
}
return inputValue.indexOf(delimiter) > -1 ? inputValue.split(delimiter) : [inputValue];
}
return [];
}
/**
* Updates the given attributes of the given element.
*/
static updateElementAttributes(element: HTMLElement, observedAttributes: string[], configuredAttributes: Record<string, any>): void {
observedAttributes.forEach((attribute) => {
if (typeof configuredAttributes[attribute] === 'undefined') {
if (element.hasAttribute(attribute)) {
element.removeAttribute(attribute);
}
} else if (('' + configuredAttributes[attribute]) !== element.getAttribute(attribute)) {
element.setAttribute(attribute, configuredAttributes[attribute]);
}
});
}
/**
* Removes the given listener of the given old event on the element with the given old ID and adds the listener of
* the given new event on the element with the given new ID.
*/
static updateListener(
listener: (event: any) => void,
parentElement: HTMLElement,
oldElementId: string,
oldEventName: string,
newElementId: string,
newEventName: string
): void {
CoreUtil.removeListener(listener, parentElement, oldElementId, oldEventName);
CoreUtil.addListener(listener, parentElement, newElementId, newEventName);
}
/**
* Updates the filtered values of all the given filter fields in the given Map using the filters in the given filter collection.
*/
static updateValuesFromListFilters(
fields: FieldConfig[],
filters: FilterCollection,
fieldsToValues: Map<string, any[]>,
createListFilterDesign: (field: FieldConfig, values?: any[]) => ListFilterDesign
): Map<string, any> {
fields.filter((field) => !!field.columnName).forEach((field) => {
const listFilters: ListFilter[] = filters.getCompatibleFilters(createListFilterDesign(field)) as ListFilter[];
fieldsToValues.set(field.columnName, CoreUtil.retrieveValuesFromListFilters(listFilters));
});
return fieldsToValues;
}
}