@isrd-isi-edu/ermrestjs 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +55 -0
- package/dist/ermrest.d.ts +3481 -0
- package/dist/ermrest.js +45 -0
- package/dist/ermrest.js.gz +0 -0
- package/dist/ermrest.js.map +1 -0
- package/dist/ermrest.min.js +45 -0
- package/dist/ermrest.min.js.gz +0 -0
- package/dist/ermrest.min.js.map +1 -0
- package/dist/ermrest.ver.txt +1 -0
- package/dist/stats.html +4949 -0
- package/js/ag_reference.js +1483 -0
- package/js/core.js +4931 -0
- package/js/datapath.js +336 -0
- package/js/export.js +956 -0
- package/js/filters.js +192 -0
- package/js/format.js +344 -0
- package/js/hatrac.js +1130 -0
- package/js/json_ld_validator.js +285 -0
- package/js/parser.js +2320 -0
- package/js/setup/node.js +27 -0
- package/js/utils/helpers.js +2300 -0
- package/js/utils/json_ld_schema.js +680 -0
- package/js/utils/pseudocolumn_helpers.js +2196 -0
- package/package.json +79 -0
- package/src/index.ts +204 -0
- package/src/models/comment.ts +14 -0
- package/src/models/deferred-promise.ts +16 -0
- package/src/models/display-name.ts +5 -0
- package/src/models/errors.ts +408 -0
- package/src/models/path-prefix-alias-mapping.ts +130 -0
- package/src/models/reference/bulk-create-foreign-key-object.ts +133 -0
- package/src/models/reference/citation.ts +98 -0
- package/src/models/reference/contextualize.ts +535 -0
- package/src/models/reference/google-dataset-metadata.ts +72 -0
- package/src/models/reference/index.ts +14 -0
- package/src/models/reference/page.ts +520 -0
- package/src/models/reference/reference-aggregate-fn.ts +37 -0
- package/src/models/reference/reference.ts +2813 -0
- package/src/models/reference/related-reference.ts +467 -0
- package/src/models/reference/tuple.ts +652 -0
- package/src/models/reference-column/asset-pseudo-column.ts +498 -0
- package/src/models/reference-column/column-aggregate.ts +313 -0
- package/src/models/reference-column/facet-column.ts +1380 -0
- package/src/models/reference-column/foreign-key-pseudo-column.ts +626 -0
- package/src/models/reference-column/inbound-foreign-key-pseudo-column.ts +131 -0
- package/src/models/reference-column/index.ts +13 -0
- package/src/models/reference-column/key-pseudo-column.ts +236 -0
- package/src/models/reference-column/pseudo-column.ts +850 -0
- package/src/models/reference-column/reference-column.ts +740 -0
- package/src/models/source-object-node.ts +156 -0
- package/src/models/source-object-wrapper.ts +694 -0
- package/src/models/table-source-definitions.ts +98 -0
- package/src/services/authn.ts +43 -0
- package/src/services/catalog.ts +37 -0
- package/src/services/config.ts +202 -0
- package/src/services/error.ts +247 -0
- package/src/services/handlebars.ts +607 -0
- package/src/services/history.ts +136 -0
- package/src/services/http.ts +536 -0
- package/src/services/logger.ts +70 -0
- package/src/services/mustache.ts +0 -0
- package/src/utils/column-utils.ts +308 -0
- package/src/utils/constants.ts +526 -0
- package/src/utils/markdown-utils.ts +855 -0
- package/src/utils/reference-utils.ts +1658 -0
- package/src/utils/template-utils.ts +0 -0
- package/src/utils/type-utils.ts +89 -0
- package/src/utils/value-utils.ts +127 -0
- package/tsconfig.json +30 -0
- package/vite.config.mts +104 -0
|
@@ -0,0 +1,2300 @@
|
|
|
1
|
+
/* eslint-disable no-control-regex */
|
|
2
|
+
/* eslint-disable prettier/prettier */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
4
|
+
/* eslint-disable no-useless-escape */
|
|
5
|
+
import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from 'lz-string';
|
|
6
|
+
import moment from 'moment-timezone';
|
|
7
|
+
import { default as mustache } from 'mustache';
|
|
8
|
+
|
|
9
|
+
import $log from '@isrd-isi-edu/ermrestjs/src/services/logger';
|
|
10
|
+
import ConfigService from '@isrd-isi-edu/ermrestjs/src/services/config';
|
|
11
|
+
import { InvalidFacetOperatorError } from '@isrd-isi-edu/ermrestjs/src/models/errors';
|
|
12
|
+
import { Reference } from '@isrd-isi-edu/ermrestjs/src/models/reference';
|
|
13
|
+
|
|
14
|
+
// legacy
|
|
15
|
+
import { isObject, isObjectAndNotNull, isValidColorRGBHex, isStringAndNotEmpty } from '@isrd-isi-edu/ermrestjs/src/utils/type-utils';
|
|
16
|
+
import { fixedEncodeURIComponent } from '@isrd-isi-edu/ermrestjs/src/utils/value-utils';
|
|
17
|
+
import { renderMarkdown } from '@isrd-isi-edu/ermrestjs/src/utils/markdown-utils';
|
|
18
|
+
import {
|
|
19
|
+
_systemColumns,
|
|
20
|
+
_dataFormats,
|
|
21
|
+
_contextArray,
|
|
22
|
+
_contexts,
|
|
23
|
+
_annotations,
|
|
24
|
+
_nonSortableTypes,
|
|
25
|
+
_commentDisplayModes,
|
|
26
|
+
_facetingErrors,
|
|
27
|
+
URL_PATH_LENGTH_LIMIT,
|
|
28
|
+
_ERMrestFeatures,
|
|
29
|
+
_systemColumnNames,
|
|
30
|
+
_specialPresentation,
|
|
31
|
+
_classNames,
|
|
32
|
+
TEMPLATE_ENGINES,
|
|
33
|
+
_entryContexts,
|
|
34
|
+
_compactContexts,
|
|
35
|
+
ENV_IS_NODE,
|
|
36
|
+
} from '@isrd-isi-edu/ermrestjs/src/utils/constants';
|
|
37
|
+
import { parse } from '@isrd-isi-edu/ermrestjs/js/parser';
|
|
38
|
+
import { Column, Key } from '@isrd-isi-edu/ermrestjs/js/core';
|
|
39
|
+
import HandlebarsService from '@isrd-isi-edu/ermrestjs/src/services/handlebars';
|
|
40
|
+
import AuthnService from '@isrd-isi-edu/ermrestjs/src/services/authn';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Given a string represting a JSON document returns the compressed version of it.
|
|
44
|
+
* It will return null if the given string is not a valid JSON.
|
|
45
|
+
* @param {String} str
|
|
46
|
+
* @return {String}
|
|
47
|
+
*/
|
|
48
|
+
export function encodeFacetString(str) {
|
|
49
|
+
try {
|
|
50
|
+
JSON.parse(str);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return "";
|
|
53
|
+
}
|
|
54
|
+
return compressToEncodedURIComponent(str);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Given an object, returns the string comrpessed version of it
|
|
59
|
+
* @param {Object} obj
|
|
60
|
+
* @return {String}
|
|
61
|
+
* @memberof ERMrest
|
|
62
|
+
* @function encodeFacet
|
|
63
|
+
*/
|
|
64
|
+
export function encodeFacet(obj) {
|
|
65
|
+
return compressToEncodedURIComponent(JSON.stringify(obj,null,0));
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Turn a given encoded facet blob into a facet object
|
|
70
|
+
* @param {string} blob the encoded facet blob
|
|
71
|
+
* @param {string?} path (optional) used for better error message
|
|
72
|
+
* @returns {Object}
|
|
73
|
+
* @memberof ERMrest
|
|
74
|
+
* @function decodeFacet
|
|
75
|
+
*/
|
|
76
|
+
export function decodeFacet(blob, path) {
|
|
77
|
+
var err = new InvalidFacetOperatorError(
|
|
78
|
+
typeof path === "string" ? path : "",
|
|
79
|
+
_facetingErrors.invalidString
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
var str = decompressFromEncodedURIComponent(blob);
|
|
84
|
+
if (str === null) {
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
return JSON.parse(str);
|
|
88
|
+
} catch (exception) {
|
|
89
|
+
$log.error(exception);
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* can be used to compare the "position columns" of colsets.
|
|
96
|
+
*
|
|
97
|
+
* @private
|
|
98
|
+
* @param {Array} a an array of sorted integer values
|
|
99
|
+
* @param {Array} b an array of sorted integer values
|
|
100
|
+
* @param {boolean=} greater - whether we should do greater check instead of greater equal
|
|
101
|
+
*
|
|
102
|
+
* return,
|
|
103
|
+
* - 1 if the position in the first argument are before the second one.
|
|
104
|
+
* - -1 if the other way around.
|
|
105
|
+
* - 0 if identical
|
|
106
|
+
* Notes:
|
|
107
|
+
* - both arguments are array and sorted ascendingly
|
|
108
|
+
* - if greater argument is true, we're doing a greater check so
|
|
109
|
+
* in the identical case this function will return -1.
|
|
110
|
+
*/
|
|
111
|
+
export function compareColumnPositions(a, b, greater) {
|
|
112
|
+
for (var i = 0; i < a.length && i < b.length ; i++) {
|
|
113
|
+
if (a[i] !== b[i]) {
|
|
114
|
+
return a[i] > b[i] ? 1 : -1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// all the columns were identical and only one has extra
|
|
118
|
+
if (a.length !== b.length) {
|
|
119
|
+
return a.length > b.length ? 1 : -1;
|
|
120
|
+
}
|
|
121
|
+
return greater ? -1 : 0;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Given an object and two string (k1, k2), if object has k1 key, will
|
|
126
|
+
* rename that key to k2 instead (values that were accessible through k1
|
|
127
|
+
* key name will be moved to k2 instead)
|
|
128
|
+
* @param {Object} obj
|
|
129
|
+
* @param {String} oldKey
|
|
130
|
+
* @param {String} newKey
|
|
131
|
+
*/
|
|
132
|
+
export function renameKey(obj, oldKey, newKey) {
|
|
133
|
+
if (!isObjectAndNotNull(obj)) return;
|
|
134
|
+
if (oldKey === newKey) return;
|
|
135
|
+
if (!Object.prototype.hasOwnProperty.call(obj, oldKey)) return;
|
|
136
|
+
|
|
137
|
+
Object.defineProperty(obj, newKey, Object.getOwnPropertyDescriptor(obj, oldKey));
|
|
138
|
+
delete obj[oldKey];
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Replaces characters in strings that are illegal/unsafe for filenames.
|
|
143
|
+
* Unsafe characters are either removed or replaced by a substitute set
|
|
144
|
+
* in the optional `options` object.
|
|
145
|
+
*
|
|
146
|
+
* Illegal Characters on Various Operating Systems
|
|
147
|
+
* / ? < > \ : * | "
|
|
148
|
+
* https://kb.acronis.com/content/39790
|
|
149
|
+
*
|
|
150
|
+
* Unicode Control codes
|
|
151
|
+
* C0 0x00-0x1f & C1 (0x80-0x9f)
|
|
152
|
+
* http://en.wikipedia.org/wiki/C0_and_C1_control_codes
|
|
153
|
+
*
|
|
154
|
+
* Reserved filenames on Unix-based systems (".", "..")
|
|
155
|
+
* Reserved filenames in Windows ("CON", "PRN", "AUX", "NUL", "COM1",
|
|
156
|
+
* "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
|
157
|
+
* "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", and
|
|
158
|
+
* "LPT9") case-insesitively and with or without filename extensions.
|
|
159
|
+
*
|
|
160
|
+
* source: https://github.com/parshap/node-sanitize-filename/blob/master/index.js
|
|
161
|
+
*
|
|
162
|
+
* @param {String} str original filename
|
|
163
|
+
* @param {String=} replacement the string that the invalid characters should be replaced with
|
|
164
|
+
* @return {String} sanitized filename
|
|
165
|
+
*/
|
|
166
|
+
export function _sanitizeFilename(str, replacement) {
|
|
167
|
+
replacement = (typeof replacement == "string") ? replacement : '_';
|
|
168
|
+
|
|
169
|
+
var illegalRe = /[\/\?<>\\:\*\|":]/g;
|
|
170
|
+
var controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
|
171
|
+
var reservedRe = /^\.+$/;
|
|
172
|
+
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
|
173
|
+
var windowsTrailingRe = /[\. ]+$/;
|
|
174
|
+
return str.replace(illegalRe, replacement)
|
|
175
|
+
.replace(controlRe, replacement)
|
|
176
|
+
.replace(reservedRe, replacement)
|
|
177
|
+
.replace(windowsReservedRe, replacement)
|
|
178
|
+
.replace(windowsTrailingRe, replacement);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @private
|
|
183
|
+
* @param {Object} child child class
|
|
184
|
+
* @param {Object} parent parent class
|
|
185
|
+
* @desc
|
|
186
|
+
* This function should be called to extend a prototype with another one.
|
|
187
|
+
* Make sure to attach the right constructor to the prototypes after,
|
|
188
|
+
* and also call `child.superClass.call(this, arguments*)` in frist line of
|
|
189
|
+
* the child constructor with appropriate arguments.
|
|
190
|
+
* You can define the extra or overriden functions of child before calling _extends.
|
|
191
|
+
* This function will take care of copying those functions.
|
|
192
|
+
* *Must be called after defining parent prototype and child constructor*
|
|
193
|
+
*/
|
|
194
|
+
export function _extends(child, parent) {
|
|
195
|
+
var childFns = child.prototype;
|
|
196
|
+
child.prototype = Object.create(parent.prototype);
|
|
197
|
+
child.prototype.constructor = child;
|
|
198
|
+
child.superClass = parent;
|
|
199
|
+
child.super = parent.prototype;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Given a string, will return the existing value in the object.
|
|
204
|
+
* It will return undefined if the key doesn't exist or invalid input.
|
|
205
|
+
* @param {Object} obj The object that we want the value from
|
|
206
|
+
* @param {String} path the string path (`a.b.c`)
|
|
207
|
+
* @return {Object} value
|
|
208
|
+
*/
|
|
209
|
+
export function _getPath(obj, path) {
|
|
210
|
+
var pathNodes;
|
|
211
|
+
|
|
212
|
+
if (typeof path === "string") {
|
|
213
|
+
if (path.length === 0) {
|
|
214
|
+
return this[""];
|
|
215
|
+
}
|
|
216
|
+
pathNodes = path.split(".");
|
|
217
|
+
} else if (Array.isArray(path)) {
|
|
218
|
+
pathNodes = path;
|
|
219
|
+
} else {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (var i = 0; i < pathNodes.length; i++) {
|
|
224
|
+
if (!Object.prototype.hasOwnProperty.call(obj, pathNodes[i])) {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
obj = obj[pathNodes[i]];
|
|
228
|
+
}
|
|
229
|
+
return obj;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @function
|
|
234
|
+
* @param {String} str string to be converted.
|
|
235
|
+
* @desc
|
|
236
|
+
* Converts a string to title case (separators are space, hyphen, and underscore)
|
|
237
|
+
*/
|
|
238
|
+
export function _toTitleCase(str) {
|
|
239
|
+
return str.replace(/([^\x00-\x7F]|([^\W_]))[^\-\s_]*/g, function(txt){
|
|
240
|
+
return txt.charAt(0).toLocaleUpperCase() + txt.substr(1).toLocaleLowerCase();
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* @function
|
|
246
|
+
* @param {String} str string to be manipulated.
|
|
247
|
+
* @private
|
|
248
|
+
* @desc
|
|
249
|
+
* Replaces underline with space.
|
|
250
|
+
*/
|
|
251
|
+
export function _underlineToSpace(str) {
|
|
252
|
+
return str.replace(/_/g, ' ');
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Given an object recursively replace all the dots in the keys with underscore.
|
|
257
|
+
* This will also remove any custom JavaScript objects.
|
|
258
|
+
* NOTE: This function will ignore any objects that has been created from a custom constructor.
|
|
259
|
+
* NOTE: This function does not detect loop, make sure that your object does not have circular references.
|
|
260
|
+
*
|
|
261
|
+
* @param {Object} obj A simple javascript object. It should not include anything that is not in JSON syntax (functions, etc.).
|
|
262
|
+
* @return {Object} A new object created by:
|
|
263
|
+
* 1. Replacing the dots in keys to underscore.
|
|
264
|
+
* 2. Ignoring any custom-type objects. The given object should be JSON not JavaScript object.
|
|
265
|
+
*/
|
|
266
|
+
export function _replaceDotWithUnderscore(obj) {
|
|
267
|
+
var res = {}, val, k, newK;
|
|
268
|
+
for (k in obj) {
|
|
269
|
+
if (!Object.prototype.hasOwnProperty.call(obj, k)) continue;
|
|
270
|
+
val = obj[k];
|
|
271
|
+
|
|
272
|
+
// we don't accept custom type objects (we're not detecting circular reference)
|
|
273
|
+
if (isObject(val) && (val.constructor && val.constructor != Object)) continue;
|
|
274
|
+
|
|
275
|
+
newK = k;
|
|
276
|
+
if (k.includes(".")) {
|
|
277
|
+
// replace dot with underscore
|
|
278
|
+
newK = k.replace(/\./g,"_");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (isObject(val)) {
|
|
282
|
+
res[newK] = _replaceDotWithUnderscore(val);
|
|
283
|
+
} else {
|
|
284
|
+
res[newK] = val;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return res;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @function
|
|
292
|
+
* @param {String} regExp string to be regular expression encoded
|
|
293
|
+
* @desc converts the string into a regular expression with properly encoded characters
|
|
294
|
+
*/
|
|
295
|
+
export function _encodeRegexp(str) {
|
|
296
|
+
var stringReplaceExp = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$]/g;
|
|
297
|
+
// the first '\' escapes the second '\' which is used to escape the matched character in the returned string
|
|
298
|
+
// $& represents the matched character
|
|
299
|
+
var escapedRegexString = str.replace(stringReplaceExp, '\\$&');
|
|
300
|
+
|
|
301
|
+
return escapedRegexString;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
export function _nextChar(c) {
|
|
305
|
+
return String.fromCharCode(c.charCodeAt(0) + 1);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* @function
|
|
310
|
+
* @param {Object} element a model element (schema, table, or column)
|
|
311
|
+
* @param {boolean} useName determines whether we can use name and name_style or not
|
|
312
|
+
* @param {Object=} parentElement the upper element (schema->null, table->schema, column->table)
|
|
313
|
+
* @desc This function determines the display name for the schema, table, or
|
|
314
|
+
* column elements of a model.
|
|
315
|
+
*/
|
|
316
|
+
export function _determineDisplayName(element, useName, parentElement) {
|
|
317
|
+
var value = useName ? element.name : undefined,
|
|
318
|
+
unformatted = useName ? element.name : undefined,
|
|
319
|
+
hasDisplayName = false,
|
|
320
|
+
isHTML = false;
|
|
321
|
+
try {
|
|
322
|
+
var display_annotation = element.annotations.get(_annotations.DISPLAY);
|
|
323
|
+
if (display_annotation && display_annotation.content) {
|
|
324
|
+
|
|
325
|
+
//get the markdown display name
|
|
326
|
+
if(display_annotation.content.markdown_name) {
|
|
327
|
+
value = renderMarkdown(display_annotation.content.markdown_name, true);
|
|
328
|
+
unformatted = display_annotation.content.name ? display_annotation.content.name : display_annotation.content.markdown_name;
|
|
329
|
+
hasDisplayName = true;
|
|
330
|
+
isHTML = true;
|
|
331
|
+
}
|
|
332
|
+
//get the specified display name
|
|
333
|
+
else if (display_annotation.content.name){
|
|
334
|
+
value = display_annotation.content.name;
|
|
335
|
+
unformatted = display_annotation.content.name;
|
|
336
|
+
hasDisplayName = true;
|
|
337
|
+
isHTML = false;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
//get the name styles
|
|
341
|
+
if(useName && display_annotation.content.name_style){
|
|
342
|
+
element._nameStyle = display_annotation.content.name_style;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
} catch (exception) {
|
|
346
|
+
// no display annotation, don't do anything
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// if name styles are undefined, get them from the parent element
|
|
350
|
+
// if it's a system column, don't use the name_styles that are defined on the parent.
|
|
351
|
+
// NOTE: underline_space, title_case, markdown might be null.
|
|
352
|
+
if(parentElement && !(element instanceof Column && _systemColumns.indexOf(element.name) !== -1)){
|
|
353
|
+
if(!("underline_space" in element._nameStyle)){
|
|
354
|
+
element._nameStyle.underline_space = parentElement._nameStyle.underline_space;
|
|
355
|
+
}
|
|
356
|
+
if(!("title_case" in element._nameStyle)){
|
|
357
|
+
element._nameStyle.title_case = parentElement._nameStyle.title_case;
|
|
358
|
+
}
|
|
359
|
+
if(!("markdown" in element._nameStyle)){
|
|
360
|
+
element._nameStyle.markdown = parentElement._nameStyle.markdown;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// if name was not specified and name styles are defined, apply the heuristic functions (name styles)
|
|
365
|
+
if(useName && !hasDisplayName && element._nameStyle){
|
|
366
|
+
if(element._nameStyle.markdown){
|
|
367
|
+
value = renderMarkdown(element.name, true);
|
|
368
|
+
isHTML = true;
|
|
369
|
+
} else {
|
|
370
|
+
if(element._nameStyle.underline_space){
|
|
371
|
+
value = _underlineToSpace(value);
|
|
372
|
+
unformatted = _underlineToSpace(unformatted);
|
|
373
|
+
}
|
|
374
|
+
if(element._nameStyle.title_case){
|
|
375
|
+
value = _toTitleCase(value);
|
|
376
|
+
unformatted = _toTitleCase(unformatted);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return {"isHTML": isHTML, "value": value, "unformatted": unformatted};
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* @function
|
|
386
|
+
* @param {string} context the context that we want the value of.
|
|
387
|
+
* @param {Annotation} annotation the annotation object.
|
|
388
|
+
* @param {Boolean=} dontUseDefaultContext Whether we should use the default (*) context
|
|
389
|
+
* @desc This function returns the list that should be used for the given context.
|
|
390
|
+
* Used for visible columns and visible foreign keys.
|
|
391
|
+
*/
|
|
392
|
+
export function _getRecursiveAnnotationValue(context, annotation, dontUseDefaultContext) {
|
|
393
|
+
var contextedAnnot = _getAnnotationValueByContext(context, annotation, dontUseDefaultContext);
|
|
394
|
+
if (contextedAnnot !== -1) { // found the context
|
|
395
|
+
if (typeof contextedAnnot == "object" || (_contextArray.indexOf(contextedAnnot) === -1) ) {
|
|
396
|
+
return contextedAnnot;
|
|
397
|
+
} else {
|
|
398
|
+
return _getRecursiveAnnotationValue(contextedAnnot, annotation, dontUseDefaultContext); // go to next level
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return -1; // there was no annotation
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* @param {string} context the context that we want the value of.
|
|
407
|
+
* @param {Object} annotation the annotation object.
|
|
408
|
+
* @param {Boolean=} dontUseDefaultContext Whether we should use the default (*) context
|
|
409
|
+
* @desc returns the annotation value based on the given context.
|
|
410
|
+
*/
|
|
411
|
+
export function _getAnnotationValueByContext(context, annotation, dontUseDefaultContext) {
|
|
412
|
+
|
|
413
|
+
// check annotation is an object
|
|
414
|
+
if (typeof annotation !== "object" || annotation == null) {
|
|
415
|
+
return -1;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (typeof context === "string") {
|
|
419
|
+
// NOTE: We assume that context names are seperated with `/`
|
|
420
|
+
var partial = context,
|
|
421
|
+
parts = context.split("/");
|
|
422
|
+
while (partial !== "") {
|
|
423
|
+
if (partial in annotation) { // found the context
|
|
424
|
+
return annotation[partial];
|
|
425
|
+
}
|
|
426
|
+
parts.splice(-1,1); // remove the last part
|
|
427
|
+
partial = parts.join("/");
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// if context wasn't in the annotations but there is a default context
|
|
432
|
+
if (dontUseDefaultContext !== true && _contexts.DEFAULT in annotation) {
|
|
433
|
+
return annotation[_contexts.DEFAULT];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return -1; // there was no annotation
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* retun the value that should be used for the display setting. If missing, it will return "-1".
|
|
441
|
+
*
|
|
442
|
+
* @param {Table|ERMrest.Column|ERMrest.ForeignKeyRef} obj either table object, or an object that has `.table`
|
|
443
|
+
* @param {String} context the context string
|
|
444
|
+
* @param {String} annotKey the annotation key that you want the annotation value for
|
|
445
|
+
* @param {Boolean} isTable if the first parameter is table, you should pass `true` for this parameter
|
|
446
|
+
*/
|
|
447
|
+
export function _getHierarchicalDisplayAnnotationValue(obj, context, annotKey, isTable) {
|
|
448
|
+
var hierarichy = [obj], table, annot, value = -1;
|
|
449
|
+
var displayAnnot = _annotations.DISPLAY;
|
|
450
|
+
|
|
451
|
+
if (!isTable) {
|
|
452
|
+
table = obj.table;
|
|
453
|
+
hierarichy.push(obj.table);
|
|
454
|
+
} else {
|
|
455
|
+
table = obj;
|
|
456
|
+
}
|
|
457
|
+
hierarichy.push(table.schema, table.schema.catalog);
|
|
458
|
+
|
|
459
|
+
for (var i = 0; i < hierarichy.length; i++) {
|
|
460
|
+
if (!hierarichy[i].annotations.contains(displayAnnot)) continue;
|
|
461
|
+
|
|
462
|
+
annot = hierarichy[i].annotations.get(displayAnnot);
|
|
463
|
+
if (annot && annot.content && annot.content[annotKey]) {
|
|
464
|
+
value = _getAnnotationValueByContext(context, annot.content[annotKey]);
|
|
465
|
+
if (value !== -1) break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return value;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* @param {object} ref The object that we want the null value for.
|
|
474
|
+
* @param {string} context The context that we want the value of.
|
|
475
|
+
* @param {Array} elements All the possible levels of heirarchy (column, table, schema).
|
|
476
|
+
* @desc returns the null value for the column based on context and annotation and sets in the ref object too.
|
|
477
|
+
*/
|
|
478
|
+
export function _getNullValue(ref, context, isTable) {
|
|
479
|
+
if (context in ref._nullValue) { // use the cached value
|
|
480
|
+
return ref._nullValue[context];
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
var value = _getHierarchicalDisplayAnnotationValue(ref, context, "show_null", isTable);
|
|
484
|
+
|
|
485
|
+
// backward compatibility: try show_nulls too
|
|
486
|
+
// TODO eventually should be removed
|
|
487
|
+
if (value === -1) {
|
|
488
|
+
value = _getHierarchicalDisplayAnnotationValue(ref, context, "show_nulls", isTable);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (value === false) { //eliminate the field
|
|
492
|
+
value = null;
|
|
493
|
+
} else if (value === true) { //empty field
|
|
494
|
+
value = "";
|
|
495
|
+
} else if (typeof value !== "string") { // default
|
|
496
|
+
if (context === _contexts.DETAILED) {
|
|
497
|
+
value = null; // default null value for DETAILED context
|
|
498
|
+
} else {
|
|
499
|
+
value = ""; //default null value
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
ref._nullValue[context] = value; // cache the value
|
|
504
|
+
return value;
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* @param {Annotations} annotations - the defined annotation on the model
|
|
509
|
+
* @param {String} key - the annotation key
|
|
510
|
+
* @param {Boolean|null} defaultValue - the value that should be used if annotation is missing.
|
|
511
|
+
* (parent value or null)
|
|
512
|
+
* Returns:
|
|
513
|
+
* - true: if annotation is defined and it's not `false`.
|
|
514
|
+
* - false: if annotation is defined and it's `false`
|
|
515
|
+
* - defaultValue: if annotation is not defined
|
|
516
|
+
* @private
|
|
517
|
+
*/
|
|
518
|
+
export function _processACLAnnotation(annotations, key, defaultValue) {
|
|
519
|
+
if (annotations.contains(key)) {
|
|
520
|
+
var ndAnnot = annotations.get(key).content;
|
|
521
|
+
if (ndAnnot !== false) {
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
return defaultValue;
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
// given a reference and associated data to it, will return a list of Values
|
|
530
|
+
// corresponding to its sort object
|
|
531
|
+
export function _getPagingValues(ref, rowData, rowLinkedData) {
|
|
532
|
+
if (!rowData) {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
var loc = ref.location,
|
|
536
|
+
values = [], addedCols = {}, sortObjectNames = {},
|
|
537
|
+
col, i, j, sortCol, colName, data, fkData;
|
|
538
|
+
|
|
539
|
+
for (i = 0; i < loc.sortObject.length; i++) {
|
|
540
|
+
colName = loc.sortObject[i].column;
|
|
541
|
+
|
|
542
|
+
try {
|
|
543
|
+
col = ref.getColumnByName(colName);
|
|
544
|
+
} catch (e) {
|
|
545
|
+
return null; // column doesn't exist return null.
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// avoid duplicate sort columns
|
|
549
|
+
if (col.name in sortObjectNames) continue;
|
|
550
|
+
sortObjectNames[col.name] = true;
|
|
551
|
+
|
|
552
|
+
if (!col.sortable) {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
for (j = 0; j < col._sortColumns.length; j++) {
|
|
557
|
+
sortCol = col._sortColumns[j].column;
|
|
558
|
+
|
|
559
|
+
// avoid duplciate columns
|
|
560
|
+
if (sortCol in addedCols) continue;
|
|
561
|
+
addedCols[sortCol] = true;
|
|
562
|
+
|
|
563
|
+
if (col.isForeignKey || (col.isPathColumn && col.isUnique && col.hasPath)) {
|
|
564
|
+
fkData = rowLinkedData[col.name];
|
|
565
|
+
data = null;
|
|
566
|
+
if (isObjectAndNotNull(fkData)) {
|
|
567
|
+
data = fkData[sortCol.name];
|
|
568
|
+
}
|
|
569
|
+
} else {
|
|
570
|
+
data = rowData[sortCol.name];
|
|
571
|
+
}
|
|
572
|
+
values.push(data);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return values;
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Process the given list of column order, and return the appropriate list
|
|
580
|
+
* of objects that have:
|
|
581
|
+
* - `column`: The {@link ERMrest.Column} object.
|
|
582
|
+
* - `descending`: The boolean that Indicates whether we should reverse sort order or not.
|
|
583
|
+
*
|
|
584
|
+
* @param {string} columnOrder The object that defines the column/row order
|
|
585
|
+
* @param {Table} table
|
|
586
|
+
* @param {Object=} options the extra options:
|
|
587
|
+
* - allowNumOccurrences: to allow the specific frequency column_order
|
|
588
|
+
* @return {Array=} If it's undefined, the column_order that is defined is not valid
|
|
589
|
+
* @private
|
|
590
|
+
*/
|
|
591
|
+
export function _processColumnOrderList(columnOrder, table, options) {
|
|
592
|
+
options = options || {};
|
|
593
|
+
|
|
594
|
+
if (columnOrder === false) {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
var res, colName, descending, colNames = {}, numOccurr = false;
|
|
599
|
+
if (Array.isArray(columnOrder)) {
|
|
600
|
+
res = [];
|
|
601
|
+
for (var i = 0 ; i < columnOrder.length; i++) {
|
|
602
|
+
try {
|
|
603
|
+
if (typeof columnOrder[i] === "string") {
|
|
604
|
+
colName = columnOrder[i];
|
|
605
|
+
} else if (columnOrder[i] && columnOrder[i].column) {
|
|
606
|
+
colName = columnOrder[i].column;
|
|
607
|
+
} else if (options.allowNumOccurrences && !numOccurr && columnOrder[i] && columnOrder[i].num_occurrences) {
|
|
608
|
+
numOccurr = true;
|
|
609
|
+
// add the frequency sort
|
|
610
|
+
res.push({num_occurrences: true, descending: (columnOrder[i] && columnOrder[i].descending === true)});
|
|
611
|
+
|
|
612
|
+
continue;
|
|
613
|
+
} else {
|
|
614
|
+
continue; // invalid syntax
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const col = table.columns.get(colName);
|
|
618
|
+
|
|
619
|
+
// make sure it's sortable
|
|
620
|
+
if (_nonSortableTypes.indexOf(col.type.name) !== -1) {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// avoid duplicates
|
|
625
|
+
if (colName in colNames) {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
colNames[colName] = true;
|
|
629
|
+
|
|
630
|
+
descending = (columnOrder[i] && columnOrder[i].descending === true);
|
|
631
|
+
res.push({
|
|
632
|
+
column: col,
|
|
633
|
+
descending: descending,
|
|
634
|
+
});
|
|
635
|
+
} catch(exception) {
|
|
636
|
+
// ignore
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return res; // it might be undefined
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Given the source object and default comment props, will return the comment that should be used.
|
|
645
|
+
* @returns {CommentType}
|
|
646
|
+
* @private
|
|
647
|
+
*/
|
|
648
|
+
export function _processSourceObjectComment(sourceObject, defaultComment, defaultCommentRenderMd, defaultDisplayMode) {
|
|
649
|
+
if (sourceObject && _isValidModelComment(sourceObject.comment)) {
|
|
650
|
+
defaultComment = sourceObject.comment;
|
|
651
|
+
}
|
|
652
|
+
if (sourceObject && _isValidModelCommentDisplay(sourceObject.comment_display)) {
|
|
653
|
+
defaultDisplayMode = sourceObject.comment_display;
|
|
654
|
+
}
|
|
655
|
+
if (sourceObject && typeof sourceObject.comment_render_markdown === 'boolean') {
|
|
656
|
+
defaultCommentRenderMd = sourceObject.comment_render_markdown;
|
|
657
|
+
}
|
|
658
|
+
return _processModelComment(defaultComment, defaultCommentRenderMd, defaultDisplayMode);
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Turn a comment annotaiton/string value into a proper comment object.
|
|
663
|
+
* @param {string|null|false} comment
|
|
664
|
+
* @param {boolean=} isMarkdown whether the given comment should be rendered as markdown (default: true).
|
|
665
|
+
* @param {string=} displayMode the display mode of the comment (inline, tooltip)
|
|
666
|
+
* @private
|
|
667
|
+
*/
|
|
668
|
+
export function _processModelComment(comment, isMarkdown, displayMode) {
|
|
669
|
+
if (comment !== false && typeof comment !== 'string') {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
var usedDisplayMode = _isValidModelCommentDisplay(displayMode) ? displayMode : _commentDisplayModes.tooltip;
|
|
674
|
+
if (comment === false) {
|
|
675
|
+
return { isHTML: false, unformatted: '', value: '', displayMode: usedDisplayMode };
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return {
|
|
679
|
+
isHTML: isMarkdown !== false,
|
|
680
|
+
unformatted: comment,
|
|
681
|
+
value: (isMarkdown !== false && comment.length > 0) ? renderMarkdown(comment) : comment,
|
|
682
|
+
displayMode: usedDisplayMode
|
|
683
|
+
};
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Given an input string for the comment, will return true or false depending if the comment is of a valid type and value
|
|
688
|
+
* - if =string : returns true.
|
|
689
|
+
* - if =false: returns true.
|
|
690
|
+
* - otherwise returns false
|
|
691
|
+
* @private
|
|
692
|
+
*/
|
|
693
|
+
export function _isValidModelComment(comment) {
|
|
694
|
+
return typeof comment === "string" || comment === false;
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Given an input string for the comment display, will return true or false depending if the display value is of a valid type and value
|
|
699
|
+
* - if =string && (="tooltip" || ="inline") : returns true.
|
|
700
|
+
* - otherwise returns false
|
|
701
|
+
* @private
|
|
702
|
+
*/
|
|
703
|
+
export function _isValidModelCommentDisplay(display) {
|
|
704
|
+
return typeof display === "string" && _commentDisplayModes[display] !== -1;
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Given a foreign key name, will return true or false depending if the name value is of a valid type and value
|
|
709
|
+
* - if =['', ''] : returns true
|
|
710
|
+
* - otherwise returns false
|
|
711
|
+
*
|
|
712
|
+
* @private
|
|
713
|
+
*/
|
|
714
|
+
export function _isValidForeignKeyName(fkName) {
|
|
715
|
+
return Array.isArray(fkName) && fkName.length === 2 && typeof fkName[0] === 'string' && typeof fkName[1] === 'string';
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Given input value for bulk_create_foreign_key, will return true or false depending if the value is of a valid type and value for the bulk_create_foreign_key
|
|
720
|
+
* - if =false | =null | =['', ''] : returns true
|
|
721
|
+
* - otherwise returns false
|
|
722
|
+
*
|
|
723
|
+
* @private
|
|
724
|
+
*/
|
|
725
|
+
export function _isValidBulkCreateForeignKey(bulkCreateProp) {
|
|
726
|
+
return bulkCreateProp === false || bulkCreateProp === null || _isValidForeignKeyName(bulkCreateProp);
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* @function
|
|
731
|
+
* @param {Table} table The object that we want the formatted values for.
|
|
732
|
+
* @param {String} context the context that we want the formatted values for.
|
|
733
|
+
* @param {object} data The object which contains key value pairs of data to be transformed
|
|
734
|
+
* @param {object=} linkedData The object which contains key value paris of foreign key data.
|
|
735
|
+
* @return {any} A formatted keyvalue pair of object
|
|
736
|
+
* @desc Returns a formatted keyvalue pairs of object as a result of using `col.formatvalue`.
|
|
737
|
+
* If you want the formatted value of a single column, you should call formatvalue,
|
|
738
|
+
* this function is written for the purpose of being used in markdown.
|
|
739
|
+
* @private
|
|
740
|
+
*/
|
|
741
|
+
export function _getFormattedKeyValues(table, context, data, linkedData) {
|
|
742
|
+
var keyValues, k, fkData, col, cons, rowname, v;
|
|
743
|
+
|
|
744
|
+
var getTableValues = function (d, currTable) {
|
|
745
|
+
var res = {};
|
|
746
|
+
currTable.sourceDefinitions.columns.forEach(function (col) {
|
|
747
|
+
if (!(col.name in d)) return;
|
|
748
|
+
|
|
749
|
+
try {
|
|
750
|
+
k = col.name;
|
|
751
|
+
v = col.formatvalue(d[k], context);
|
|
752
|
+
if (col.type.isArray) {
|
|
753
|
+
v = _formatUtils.printArray(v, {isMarkdown: true});
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
res[k] = v;
|
|
757
|
+
res["_" + k] = d[k];
|
|
758
|
+
|
|
759
|
+
// alternative names
|
|
760
|
+
// TODO this should change to allow usage of table column names.
|
|
761
|
+
if (Array.isArray(currTable.sourceDefinitions.sourceMapping[k]) ){
|
|
762
|
+
currTable.sourceDefinitions.sourceMapping[k].forEach(function (altKey) {
|
|
763
|
+
res[altKey] = v;
|
|
764
|
+
res["_" + altKey] = d[k];
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
} catch (e) {
|
|
768
|
+
// if the value is invalid (for example hatrac TODO can be imporved)
|
|
769
|
+
res[k] = d[k];
|
|
770
|
+
res["_" + k] = d[k];
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
return res;
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
// get the data from current table
|
|
777
|
+
keyValues = getTableValues(data, table);
|
|
778
|
+
|
|
779
|
+
//get foreignkey data if available
|
|
780
|
+
if (linkedData && typeof linkedData === "object" && table.sourceDefinitions.fkeys.length > 0) {
|
|
781
|
+
keyValues.$fkeys = {};
|
|
782
|
+
table.sourceDefinitions.fkeys.forEach(function (fk) {
|
|
783
|
+
var p = _generateRowLinkProperties(fk.key, linkedData[fk.name], context);
|
|
784
|
+
if (!p) return;
|
|
785
|
+
|
|
786
|
+
cons = fk.constraint_names[0];
|
|
787
|
+
if (!keyValues.$fkeys[cons[0]]) {
|
|
788
|
+
keyValues.$fkeys[cons[0]] = {};
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
var fkTempVal = {
|
|
792
|
+
"values": getTableValues(linkedData[fk.name], fk.key.table),
|
|
793
|
+
"rowName": p.unformatted,
|
|
794
|
+
"uri": {
|
|
795
|
+
"detailed": p.reference.contextualize.detailed.appLink
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
// the new format
|
|
800
|
+
keyValues["$fkey_" + cons[0] + "_" + cons[1]] = fkTempVal;
|
|
801
|
+
|
|
802
|
+
// the old format
|
|
803
|
+
keyValues.$fkeys[cons[0]][cons[1]] = fkTempVal;
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return keyValues;
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* @param {string[]} columnNames Array of column names
|
|
812
|
+
* @return {string|false} the column name. if couldn't find any columns will return false.
|
|
813
|
+
* @private
|
|
814
|
+
*/
|
|
815
|
+
export function _getCandidateRowNameColumn(columnNames) {
|
|
816
|
+
var candidates = [
|
|
817
|
+
'title', 'name', 'term', 'label', 'accessionid', 'accessionnumber'
|
|
818
|
+
];
|
|
819
|
+
|
|
820
|
+
var removeExtra = function (str) { // remove `.`, `-`, `_`, and space
|
|
821
|
+
return str.replace(/[\.\s\_-]+/g, "").toLocaleLowerCase();
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
825
|
+
for (var j = 0; j < columnNames.length; j++) {
|
|
826
|
+
if (candidates[i] === removeExtra(columnNames[j])) {
|
|
827
|
+
return columnNames[j];
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// no candidate columns found
|
|
833
|
+
return false;
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* returns an object with the following attributes:
|
|
838
|
+
* - values: the formatted and unformatted values
|
|
839
|
+
* - rowName: a rowname object.
|
|
840
|
+
* - uri.detailed: applink to detailed for the row
|
|
841
|
+
* @private
|
|
842
|
+
* @param {Table} table the table object
|
|
843
|
+
* @param {string} context current context
|
|
844
|
+
* @param {Object} data the raw data
|
|
845
|
+
* @param {any=} linkedData the raw data of foreignkeys
|
|
846
|
+
* @param {Key=} key the alternate key to use
|
|
847
|
+
* @return {{values: Record<string, any>, rowName: {value: string, isHTML: boolean, unformatted: string}, uri: {detailed: string}}}
|
|
848
|
+
*/
|
|
849
|
+
export function _getRowTemplateVariables(table, context, data, linkedData, key) {
|
|
850
|
+
var uri = _generateRowURI(table, data, key);
|
|
851
|
+
if (uri == null) return {};
|
|
852
|
+
var ref = new Reference(parse(uri), table.schema.catalog);
|
|
853
|
+
return {
|
|
854
|
+
values: _getFormattedKeyValues(table, context, data, linkedData),
|
|
855
|
+
rowName: _generateRowName(table, context, data, linkedData).unformatted,
|
|
856
|
+
uri: {
|
|
857
|
+
detailed: ref.contextualize.detailed.appLink
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Given the available linked data, generate the uniqueId for the row this data represents given the shortest key of the table
|
|
864
|
+
*
|
|
865
|
+
* @param {Column[]} tableShortestKey shortest key from the table the linkedData is for
|
|
866
|
+
* @param {Object} data data to use to generate the unique id
|
|
867
|
+
* @returns string | null - unique id for the row the linkedData represents
|
|
868
|
+
*/
|
|
869
|
+
export function _generateTupleUniqueId(tableShortestKey, data) {
|
|
870
|
+
let hasNull = false, _uniqueId = "";
|
|
871
|
+
|
|
872
|
+
for (var i = 0; i < tableShortestKey.length; i++) {
|
|
873
|
+
const col = tableShortestKey[i];
|
|
874
|
+
const keyName = col.name;
|
|
875
|
+
if (data[keyName] == null) {
|
|
876
|
+
hasNull = true;
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
if (i !== 0) _uniqueId += "_";
|
|
880
|
+
const isJSON = col.type.name === 'json' || col.type.name === 'jsonb';
|
|
881
|
+
// if the column is JSON, we need to stringify it otherwise it will print [object Object]
|
|
882
|
+
_uniqueId += isJSON ? JSON.stringify(data[keyName], undefined, 0) : data[keyName];
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (hasNull) {
|
|
886
|
+
_uniqueId = null;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return _uniqueId;
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* @function
|
|
894
|
+
* @param {Table} table The table that we want the row name for.
|
|
895
|
+
* @param {String} context Current context.
|
|
896
|
+
* @param {object} data The object which contains key value pairs of data.
|
|
897
|
+
* @param {Object} linkedData The object which contains key value pairs of foreign key data.
|
|
898
|
+
* @param {boolean} isTitle determines Whether we want rowname for title or not
|
|
899
|
+
* @returns {{value: string, isHTML: boolean, unformatted: string}} The displayname object for the row. It includes has value, isHTML, and unformatted.
|
|
900
|
+
* @desc Returns the row name (html) using annotation or heuristics.
|
|
901
|
+
* @private
|
|
902
|
+
*/
|
|
903
|
+
export function _generateRowName(table, context, data, linkedData, isTitle) {
|
|
904
|
+
var annotation, col, template, keyValues, pattern, actualContext;
|
|
905
|
+
|
|
906
|
+
var templateVariables = _getFormattedKeyValues(table, context, data, linkedData);
|
|
907
|
+
|
|
908
|
+
// If table has table-display annotation then set it in annotation variable
|
|
909
|
+
if (table.annotations && table.annotations.contains(_annotations.TABLE_DISPLAY)) {
|
|
910
|
+
actualContext = isTitle ? "title" : (typeof context === "string" && context !== "*" ? context : "");
|
|
911
|
+
annotation = _getRecursiveAnnotationValue(
|
|
912
|
+
[_contexts.ROWNAME, actualContext].join("/"),
|
|
913
|
+
table.annotations.get(_annotations.TABLE_DISPLAY).content
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// if annotation is populated and annotation has display.rowName property
|
|
918
|
+
if (annotation && typeof annotation.row_markdown_pattern === 'string') {
|
|
919
|
+
template = annotation.row_markdown_pattern;
|
|
920
|
+
|
|
921
|
+
pattern = _renderTemplate(template, templateVariables, table.schema.catalog, {templateEngine: annotation.template_engine});
|
|
922
|
+
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// annotation was not defined, or it's producing empty string.
|
|
926
|
+
if (pattern == null || pattern.trim() === '') {
|
|
927
|
+
|
|
928
|
+
// no row_name annotation, use column with title, name, term
|
|
929
|
+
var candidate = _getCandidateRowNameColumn(Object.keys(data)), result;
|
|
930
|
+
if (candidate !== false) {
|
|
931
|
+
result = templateVariables[candidate];
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (!result) {
|
|
935
|
+
|
|
936
|
+
// no title, name, term, label column: use id:text type
|
|
937
|
+
// Check for id column whose type should not be integer or serial
|
|
938
|
+
var idCol = table.columns.all().filter(function (c) {
|
|
939
|
+
return ((c.name.toLowerCase() === "id") && (c.type.name.indexOf('serial') === -1) && (c.type.name.indexOf('int') === -1));
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
// no id:text, use the unique key
|
|
944
|
+
// If id column exists
|
|
945
|
+
if (idCol.length && typeof data[idCol[0].name] === 'string') {
|
|
946
|
+
|
|
947
|
+
result = templateVariables[idCol[0].name];
|
|
948
|
+
|
|
949
|
+
} else {
|
|
950
|
+
|
|
951
|
+
// Get the columns for displaykey
|
|
952
|
+
var keyColumns = table.displayKey;
|
|
953
|
+
|
|
954
|
+
// TODO this check needs to change. it is supposed to check if the table has a key or not
|
|
955
|
+
// if (keyColumns.length >= table.columns.length) {
|
|
956
|
+
// return null;
|
|
957
|
+
// }
|
|
958
|
+
|
|
959
|
+
var values = [];
|
|
960
|
+
|
|
961
|
+
// Iterate over the keycolumns to get their formatted values for `row_name` context
|
|
962
|
+
keyColumns.forEach(function (c) {
|
|
963
|
+
var value = templateVariables[c.name];
|
|
964
|
+
values.push(value);
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
/*
|
|
968
|
+
* join all values by ':' to get the display_name
|
|
969
|
+
* Eg: displayName for values=["12", "DNA results for human specimen"] would be
|
|
970
|
+
* "12:DNA results for human specimen"
|
|
971
|
+
*/
|
|
972
|
+
result = values.join(':');
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
template = "{{{name}}}";
|
|
977
|
+
keyValues = {"name": result};
|
|
978
|
+
|
|
979
|
+
// get templated patten after replacing the values using Mustache
|
|
980
|
+
pattern = _renderTemplate(template, keyValues, table.schema.catalog);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Render markdown content for the pattern
|
|
984
|
+
if (pattern == null || pattern.trim() === '') {
|
|
985
|
+
return {"value": "", "unformatted": ""};
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
return {
|
|
989
|
+
"value": renderMarkdown(pattern, true),
|
|
990
|
+
"unformatted": pattern,
|
|
991
|
+
"isHTML": true
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* @function
|
|
998
|
+
* @desc Given a key object, will return the presentation object that can bse used for it
|
|
999
|
+
* @param {Key} key the key object
|
|
1000
|
+
* @param {object} data the data for the table that key is from
|
|
1001
|
+
* @param {string} context the context string
|
|
1002
|
+
* @param {object=} templateVariables
|
|
1003
|
+
* @return {object} the presentation object that can be used for the key
|
|
1004
|
+
* (it has `isHTML`, `value`, and `unformatted`).
|
|
1005
|
+
* NOTE the function might return `null`.
|
|
1006
|
+
* @private
|
|
1007
|
+
*/
|
|
1008
|
+
export function _generateKeyPresentation(key, data, context, templateVariables, addLink) {
|
|
1009
|
+
// if data is empty
|
|
1010
|
+
if (typeof data === "undefined" || data === null || Object.keys(data).length === 0) {
|
|
1011
|
+
return null;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
var value, caption, unformatted, i;
|
|
1015
|
+
var cols = key.colset.columns,
|
|
1016
|
+
rowURI = _generateRowURI(key.table, data, key);
|
|
1017
|
+
|
|
1018
|
+
// if any of key columns don't have data, this link is not valid.
|
|
1019
|
+
if (rowURI == null) {
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// make sure that templateVariables is defined
|
|
1024
|
+
if (!isObjectAndNotNull(templateVariables)) {
|
|
1025
|
+
templateVariables = _getFormattedKeyValues(key.table, context, data);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// use the markdown_pattern that is defiend in key-display annotation
|
|
1029
|
+
var display = key.getDisplay(context);
|
|
1030
|
+
if (display.isMarkdownPattern) {
|
|
1031
|
+
unformatted = _renderTemplate(
|
|
1032
|
+
display.markdownPattern,
|
|
1033
|
+
templateVariables,
|
|
1034
|
+
key.table.schema.catalog,
|
|
1035
|
+
{templateEngine: display.templateEngine}
|
|
1036
|
+
);
|
|
1037
|
+
unformatted = (unformatted === null || unformatted.trim() === '') ? "" : unformatted;
|
|
1038
|
+
caption = renderMarkdown(unformatted, true);
|
|
1039
|
+
} else {
|
|
1040
|
+
var values = [], unformattedValues = [];
|
|
1041
|
+
|
|
1042
|
+
// create the caption
|
|
1043
|
+
var presentation;
|
|
1044
|
+
for (i = 0; i < cols.length; i++) {
|
|
1045
|
+
try {
|
|
1046
|
+
presentation = cols[i].formatPresentation(data, context, templateVariables);
|
|
1047
|
+
values.push(presentation.value);
|
|
1048
|
+
unformattedValues.push(presentation.unformatted);
|
|
1049
|
+
} catch (exception) {
|
|
1050
|
+
// the value doesn't exist
|
|
1051
|
+
return null;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
caption = values.join(":");
|
|
1055
|
+
unformatted = unformattedValues.join(":");
|
|
1056
|
+
|
|
1057
|
+
// if the caption is empty we cannot add any link to that.
|
|
1058
|
+
if (caption.trim() === '') {
|
|
1059
|
+
return null;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (!addLink || caption.match(/<a\b.+href=/)) {
|
|
1064
|
+
value = caption;
|
|
1065
|
+
} else {
|
|
1066
|
+
var keyRef = new Reference(parse(rowURI), key.table.schema.catalog);
|
|
1067
|
+
var appLink = keyRef.contextualize.detailed.appLink;
|
|
1068
|
+
|
|
1069
|
+
value = '<a href="' + appLink +'">' + caption + '</a>';
|
|
1070
|
+
unformatted = "[" + unformatted + "](" + appLink + ")";
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
return {isHTML: true, value: value, unformatted: unformatted};
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* @function
|
|
1078
|
+
* @private
|
|
1079
|
+
* @desc Given the key of a table, and data for one row will return the
|
|
1080
|
+
* presentation object for the row.
|
|
1081
|
+
* @param {Key} key the key of the table
|
|
1082
|
+
* @param {String} context Current context
|
|
1083
|
+
* @param {object} data Data for the table that this key is referring to.
|
|
1084
|
+
* @param {boolean} addLink whether the function should attach link or just the rowname.
|
|
1085
|
+
* @return an object with `caption`, and `reference` object which can be used for getting uri.
|
|
1086
|
+
*/
|
|
1087
|
+
export function _generateRowPresentation(key, data, context, addLink) {
|
|
1088
|
+
var presentation = _generateRowLinkProperties(key, data, context);
|
|
1089
|
+
|
|
1090
|
+
if (!presentation) {
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
var value, unformatted, appLink;
|
|
1095
|
+
|
|
1096
|
+
// if we don't want link, or caption has a link, or or context is EDIT: don't add the link.
|
|
1097
|
+
// create the link using reference.
|
|
1098
|
+
if (!addLink || presentation.caption.match(/<a\b.+href=/) || _isEntryContext(context)) {
|
|
1099
|
+
value = presentation.caption;
|
|
1100
|
+
unformatted = presentation.unformatted;
|
|
1101
|
+
} else {
|
|
1102
|
+
appLink = presentation.reference.contextualize.detailed.appLink;
|
|
1103
|
+
value = '<a href="' + appLink + '">' + presentation.caption + '</a>';
|
|
1104
|
+
unformatted = "[" + presentation.unformatted + "](" + appLink + ")";
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
return {isHTML: true, value: value, unformatted: unformatted};
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* Given a table object and raw data for a row, return a uri to that row with fitlers.
|
|
1112
|
+
* @param {Table} table the table object
|
|
1113
|
+
* @param {Object} raw data for the row
|
|
1114
|
+
* @param {Key=} key if we want the link based on a specific key
|
|
1115
|
+
* @return {String|null} filter that represents the current row. If row data
|
|
1116
|
+
* is missing, it will return null.
|
|
1117
|
+
*/
|
|
1118
|
+
export function _generateRowURI(table, data, key) {
|
|
1119
|
+
if (data == null) return null;
|
|
1120
|
+
|
|
1121
|
+
var cols = (isObjectAndNotNull(key) && key.colset) ? key.colset.columns : table.shortestKey;
|
|
1122
|
+
var keyPair = "", col, i;
|
|
1123
|
+
for (i = 0; i < cols.length; i++) {
|
|
1124
|
+
col = cols[i].name;
|
|
1125
|
+
if (data[col] == null) return null;
|
|
1126
|
+
keyPair += fixedEncodeURIComponent(col) + "=" + fixedEncodeURIComponent(data[col]);
|
|
1127
|
+
if (i != cols.length - 1) {
|
|
1128
|
+
keyPair += "&";
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
return table.uri + "/" + keyPair;
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* @function
|
|
1136
|
+
* @private
|
|
1137
|
+
* @param {Key} key key of the table
|
|
1138
|
+
* @param {string} context current context
|
|
1139
|
+
* @param {object} data data for the table that this key is referring to
|
|
1140
|
+
* @return {object} an object with the following attributes:
|
|
1141
|
+
* - `caption`: The caption that can be used to refer to this row in a link
|
|
1142
|
+
* - `unformatted`: The unformatted version of caption.
|
|
1143
|
+
* - `refernece`: The reference object that can be used for generating link to the row
|
|
1144
|
+
* @desc
|
|
1145
|
+
* Creates the properies for generating a link to the given row of data.
|
|
1146
|
+
* It might return `null`.
|
|
1147
|
+
*/
|
|
1148
|
+
export function _generateRowLinkProperties(key, data, context) {
|
|
1149
|
+
|
|
1150
|
+
// if data is empty
|
|
1151
|
+
if (typeof data === "undefined" || data === null || Object.keys(data).length === 0) {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
var value, rowname, i, caption, unformatted;
|
|
1156
|
+
var table = key.table;
|
|
1157
|
+
var rowURI = _generateRowURI(table, data, key);
|
|
1158
|
+
|
|
1159
|
+
// if any of key columns don't have data, this link is not valid.
|
|
1160
|
+
if (rowURI == null) {
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// use row name as the caption
|
|
1165
|
+
rowname = _generateRowName(table, context, data);
|
|
1166
|
+
caption = rowname.value;
|
|
1167
|
+
unformatted = rowname.unformatted;
|
|
1168
|
+
|
|
1169
|
+
// use key for displayname: "col_1:col_2:col_3"
|
|
1170
|
+
if (caption.trim() === '') {
|
|
1171
|
+
var templateVariables = _getFormattedKeyValues(table, context, data),
|
|
1172
|
+
formattedKeyCols = [],
|
|
1173
|
+
unformattedKeyCols = [],
|
|
1174
|
+
pres, col;
|
|
1175
|
+
|
|
1176
|
+
for (i = 0; i < key.colset.columns.length; i++) {
|
|
1177
|
+
col = key.colset.columns[i];
|
|
1178
|
+
pres = col.formatPresentation(data, context, {templateVariables: templateVariables});
|
|
1179
|
+
formattedKeyCols.push(pres.value);
|
|
1180
|
+
unformattedKeyCols.push(pres.unformatted);
|
|
1181
|
+
}
|
|
1182
|
+
caption = formattedKeyCols.join(":");
|
|
1183
|
+
unformatted = unformattedKeyCols.join(":");
|
|
1184
|
+
|
|
1185
|
+
if (caption.trim() === '') {
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// use the shortest key if it has data (for shorter url).
|
|
1191
|
+
var shortestKeyURI = _generateRowURI(table, data);
|
|
1192
|
+
if (shortestKeyURI != null) {
|
|
1193
|
+
rowURI = shortestKeyURI;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
return {
|
|
1197
|
+
unformatted: unformatted,
|
|
1198
|
+
caption: caption,
|
|
1199
|
+
reference: new Reference(parse(rowURI), table.schema.catalog)
|
|
1200
|
+
};
|
|
1201
|
+
};
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Generate the filter based on the given key and data.
|
|
1205
|
+
* The return object has the following properties:
|
|
1206
|
+
* - successful: whether we encounter any issues or not
|
|
1207
|
+
* - filters: If successful, it will be an array of {path, keyData}
|
|
1208
|
+
* - hasNull: If failed, it will signal that the issue was related to null value
|
|
1209
|
+
* for a column. `column` property will return the column name that had null value.
|
|
1210
|
+
* @param {Column[]} keyColumns
|
|
1211
|
+
* @param {Object} data
|
|
1212
|
+
* @param {Catalog} catalogObject
|
|
1213
|
+
* @param {number} pathOffsetLength the length of offset that should be considered for length limitation logic.
|
|
1214
|
+
* if the given value is negative, we will not check the url length limitation.
|
|
1215
|
+
* @param {string} displayname the displayname of reference, used for error message
|
|
1216
|
+
*/
|
|
1217
|
+
export function generateKeyValueFilters(keyColumns, data, catalogObject, pathOffsetLength, displayname) {
|
|
1218
|
+
var encode = fixedEncodeURIComponent, pathLimit = URL_PATH_LENGTH_LIMIT;
|
|
1219
|
+
|
|
1220
|
+
// see if the quantified syntax can be used
|
|
1221
|
+
var canUseQuantified = false;
|
|
1222
|
+
if (keyColumns.length > 1 || data.length === 1) {
|
|
1223
|
+
canUseQuantified = false;
|
|
1224
|
+
}
|
|
1225
|
+
else if (catalogObject && keyColumns.length === 1 && keyColumns[0].name === _systemColumnNames.RID) {
|
|
1226
|
+
canUseQuantified = catalogObject.features[_ERMrestFeatures.QUANTIFIED_RID_LISTS];
|
|
1227
|
+
} else if (catalogObject) {
|
|
1228
|
+
canUseQuantified = catalogObject.features[_ERMrestFeatures.QUANTIFIED_VALUE_LISTS];
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
var result = []; // computed, keyData
|
|
1232
|
+
|
|
1233
|
+
var keyData = [], filter = '', currentPath = '', keyColName, keyColVal, keyColumnsData;
|
|
1234
|
+
for (var rowIndex = 0; rowIndex < data.length; rowIndex++) {
|
|
1235
|
+
var rowData = data[rowIndex];
|
|
1236
|
+
if (canUseQuantified) {
|
|
1237
|
+
keyColName = keyColumns[0].name;
|
|
1238
|
+
keyColVal = rowData[keyColName];
|
|
1239
|
+
keyColumnsData = {};
|
|
1240
|
+
|
|
1241
|
+
if (keyColVal === undefined || keyColVal === null) {
|
|
1242
|
+
return {
|
|
1243
|
+
successful: false,
|
|
1244
|
+
message: "One or more " + displayname + " records have a null value for " + keyColName + ".",
|
|
1245
|
+
hasNull: true,
|
|
1246
|
+
column: keyColName
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
filter = encode(keyColVal);
|
|
1251
|
+
keyColumnsData[keyColName] = keyColVal;
|
|
1252
|
+
|
|
1253
|
+
// 6: for `=any()`
|
|
1254
|
+
// +1 is for the `,` that we're going to add
|
|
1255
|
+
// <pathOffset/><col>=any(<filter>,)
|
|
1256
|
+
if (rowIndex !== 0 && pathOffsetLength >= 0 &&
|
|
1257
|
+
(pathOffsetLength + encode(keyColName).length + 6 + currentPath.length + (rowIndex != 0 ? 1 : 0) + filter.length) > pathLimit) {
|
|
1258
|
+
result.push({
|
|
1259
|
+
path: encode(keyColName) + '=any(' + currentPath + ')',
|
|
1260
|
+
keyData: keyData
|
|
1261
|
+
});
|
|
1262
|
+
currentPath = '';
|
|
1263
|
+
keyData = [];
|
|
1264
|
+
} else if (rowIndex != 0) {
|
|
1265
|
+
filter = ',' + filter;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
currentPath += filter;
|
|
1269
|
+
keyData.push(keyColumnsData);
|
|
1270
|
+
} else {
|
|
1271
|
+
keyColumnsData = {};
|
|
1272
|
+
filter = keyColumns.length > 1 ? '(' : '';
|
|
1273
|
+
// add the values for the current row
|
|
1274
|
+
for (var keyIndex = 0; keyIndex < keyColumns.length; keyIndex++) {
|
|
1275
|
+
keyColName = keyColumns[keyIndex].name;
|
|
1276
|
+
keyColVal = rowData[keyColName];
|
|
1277
|
+
|
|
1278
|
+
if (keyColVal === undefined || keyColVal === null) {
|
|
1279
|
+
return {successful: false, message: "One or more records have a null value for " + keyColName, hasNull: true, column: keyColName};
|
|
1280
|
+
}
|
|
1281
|
+
if (keyIndex != 0) filter += '&';
|
|
1282
|
+
filter += encode(keyColName) + '=' + encode(keyColVal);
|
|
1283
|
+
keyColumnsData[keyColName] = keyColVal;
|
|
1284
|
+
}
|
|
1285
|
+
filter += keyColumns.length > 1 ? ')' : '';
|
|
1286
|
+
|
|
1287
|
+
// check url length limit if not first one;
|
|
1288
|
+
if (rowIndex != 0 && pathOffsetLength >= 0 &&
|
|
1289
|
+
(pathOffsetLength + currentPath.length + (rowIndex != 0 ? ';' : '') + filter).length > pathLimit) {
|
|
1290
|
+
// any more filters will go over the url length limit so save the current path and count
|
|
1291
|
+
// then clear both to start creating a new path
|
|
1292
|
+
result.push({
|
|
1293
|
+
path: currentPath,
|
|
1294
|
+
keyData: keyData
|
|
1295
|
+
});
|
|
1296
|
+
currentPath = '';
|
|
1297
|
+
keyData = [];
|
|
1298
|
+
} else if (rowIndex != 0) {
|
|
1299
|
+
// prepend the conjunction operator when it isn't the first filter to create and we aren't dealing with a url length limit
|
|
1300
|
+
filter = ";" + filter;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// append the filter either on the previous path after adding ";", or on the new path started from compactPath
|
|
1304
|
+
currentPath += filter;
|
|
1305
|
+
keyData.push(keyColumnsData);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// After last iteration of loop, push the current path
|
|
1310
|
+
if (canUseQuantified) {
|
|
1311
|
+
result.push({
|
|
1312
|
+
path: encode(keyColName) + '=any(' + currentPath + ')',
|
|
1313
|
+
keyData: keyData
|
|
1314
|
+
});
|
|
1315
|
+
} else {
|
|
1316
|
+
result.push({
|
|
1317
|
+
path: currentPath,
|
|
1318
|
+
keyData: keyData
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
return {successful: true, filters: result};
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
export function _stringToDate(_date, _format, _delimiter) {
|
|
1326
|
+
var formatLowerCase=_format.toLowerCase();
|
|
1327
|
+
var formatItems=formatLowerCase.split(_delimiter);
|
|
1328
|
+
var dateItems=_date.split(_delimiter);
|
|
1329
|
+
var monthIndex=formatItems.indexOf("mm");
|
|
1330
|
+
var dayIndex=formatItems.indexOf("dd");
|
|
1331
|
+
var yearIndex=formatItems.indexOf("yyyy");
|
|
1332
|
+
var month=parseInt(dateItems[monthIndex]);
|
|
1333
|
+
month-=1;
|
|
1334
|
+
var formatedDate = new Date(dateItems[yearIndex],month,dateItems[dayIndex].split(" ")[0]);
|
|
1335
|
+
return formatedDate;
|
|
1336
|
+
};
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* Given a number and precision, it will truncate it to show the given number
|
|
1340
|
+
* of digits.
|
|
1341
|
+
*
|
|
1342
|
+
*
|
|
1343
|
+
* @param {number} num
|
|
1344
|
+
* @param {number} precision
|
|
1345
|
+
* @param {number} minAllowedPrecision
|
|
1346
|
+
*/
|
|
1347
|
+
export function _toPrecision(num, precision, minAllowedPrecision) {
|
|
1348
|
+
precision = parseInt(precision);
|
|
1349
|
+
precision = isNaN(precision) || precision < minAllowedPrecision ? minAllowedPrecision : precision;
|
|
1350
|
+
|
|
1351
|
+
var isNegative = num < 0;
|
|
1352
|
+
if (isNegative) num = num * -1;
|
|
1353
|
+
|
|
1354
|
+
// this truncation logic only works because of the minimum precision that
|
|
1355
|
+
// we're allowing. if we want to allow less than that, then we should change this.
|
|
1356
|
+
var displayedNum = num.toString();
|
|
1357
|
+
var f = displayedNum.indexOf('.');
|
|
1358
|
+
if (f !== -1) {
|
|
1359
|
+
// find the number of digits after decimal point
|
|
1360
|
+
var decimalPlaces = Math.pow(10, precision - f);
|
|
1361
|
+
|
|
1362
|
+
// truncate the value
|
|
1363
|
+
displayedNum = Math.floor(num * decimalPlaces) / decimalPlaces;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// if precision is too large, the calculation might return NaN.
|
|
1367
|
+
if (isNaN(displayedNum)) {
|
|
1368
|
+
return (isNegative ? '-' : '') + num;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
return (isNegative ? '-' : '') + displayedNum;
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1374
|
+
/**
|
|
1375
|
+
* @desc An object of pretty print utility functions
|
|
1376
|
+
* @private
|
|
1377
|
+
*/
|
|
1378
|
+
export const _formatUtils = {
|
|
1379
|
+
/**
|
|
1380
|
+
* @function
|
|
1381
|
+
* @param {Object} value A boolean value to transform
|
|
1382
|
+
* @param {Object} [options] Configuration options
|
|
1383
|
+
* @return {string} A string representation of a boolean value
|
|
1384
|
+
* @desc Formats a given boolean value into a string for display
|
|
1385
|
+
*/
|
|
1386
|
+
printBoolean: function printBoolean(value, options) {
|
|
1387
|
+
options = (typeof options === 'undefined') ? {} : options;
|
|
1388
|
+
if (value === null) {
|
|
1389
|
+
return '';
|
|
1390
|
+
}
|
|
1391
|
+
return Boolean(value).toString();
|
|
1392
|
+
},
|
|
1393
|
+
|
|
1394
|
+
/**
|
|
1395
|
+
* @function
|
|
1396
|
+
* @param {Object} value An integer value to transform
|
|
1397
|
+
* @param {Object} [options] Configuration options
|
|
1398
|
+
* @return {string} A string representation of value
|
|
1399
|
+
* @desc Formats a given integer value into a whole number (with a thousands
|
|
1400
|
+
* separator if necessary), which is transformed into a string for display.
|
|
1401
|
+
*/
|
|
1402
|
+
printInteger: function printInteger(value, options) {
|
|
1403
|
+
options = (typeof options === 'undefined') ? {} : options;
|
|
1404
|
+
if (value === null) {
|
|
1405
|
+
return '';
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// Remove fractional digits
|
|
1409
|
+
value = Math.round(value);
|
|
1410
|
+
|
|
1411
|
+
// Add comma separators
|
|
1412
|
+
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
1413
|
+
},
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* @function
|
|
1417
|
+
* @param {Object} value An timestamp value to transform
|
|
1418
|
+
* @param {Object} [options] Configuration options. No options implemented so far.
|
|
1419
|
+
* @return {string} A string representation of value. Default is ISO 8601-ish like 2017-01-08 15:06:02.
|
|
1420
|
+
* @desc Formats a given timestamp value into a string for display.
|
|
1421
|
+
*/
|
|
1422
|
+
printTimestamp: function printTimestamp(value, options) {
|
|
1423
|
+
options = (typeof options === 'undefined') ? {} : options;
|
|
1424
|
+
if (value === null) {
|
|
1425
|
+
return '';
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
try {
|
|
1429
|
+
value = value.toString();
|
|
1430
|
+
} catch (exception) {
|
|
1431
|
+
$log.error("Couldn't extract timestamp from input: " + value);
|
|
1432
|
+
$log.error(exception);
|
|
1433
|
+
return '';
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
if (!moment(value).isValid()) {
|
|
1437
|
+
$log.error("Couldn't transform input to a valid timestamp: " + value);
|
|
1438
|
+
return '';
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
return moment(value).format(_dataFormats.DATETIME.display);
|
|
1442
|
+
},
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* @function
|
|
1446
|
+
* @param {Object} value A date value to transform
|
|
1447
|
+
* @param {Object} [options] Configuration options. No options implemented so far.
|
|
1448
|
+
* @return {string} A string representation of value
|
|
1449
|
+
* @desc Formats a given date[time] value into a date string for display.
|
|
1450
|
+
* If any time information is provided, it will be left off.
|
|
1451
|
+
*/
|
|
1452
|
+
printDate: function printDate(value, options) {
|
|
1453
|
+
options = (typeof options === 'undefined') ? {} : options;
|
|
1454
|
+
if (value === null) {
|
|
1455
|
+
return '';
|
|
1456
|
+
}
|
|
1457
|
+
// var year, month, date;
|
|
1458
|
+
try {
|
|
1459
|
+
value = value.toString();
|
|
1460
|
+
} catch (exception) {
|
|
1461
|
+
$log.error("Couldn't extract date info from input: " + value);
|
|
1462
|
+
$log.error(exception);
|
|
1463
|
+
return '';
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
if (!moment(value).isValid()) {
|
|
1467
|
+
$log.error("Couldn't transform input to a valid date: " + value);
|
|
1468
|
+
return '';
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
return moment(value).format(_dataFormats.DATE);
|
|
1472
|
+
},
|
|
1473
|
+
|
|
1474
|
+
/**
|
|
1475
|
+
* @function
|
|
1476
|
+
* @param {Object} value A float value to transform
|
|
1477
|
+
* @param {Object} [options] Configuration options.
|
|
1478
|
+
* - "numFracDigits" is the number of fractional digits to appear after the decimal point
|
|
1479
|
+
* @return {string} A string representation of value
|
|
1480
|
+
* @desc Formats a given float value into a string for display. Removes leading 0s; adds thousands separator.
|
|
1481
|
+
*/
|
|
1482
|
+
printFloat: function printFloat(value, options) {
|
|
1483
|
+
options = (typeof options === 'undefined') ? {} : options;
|
|
1484
|
+
|
|
1485
|
+
if (value === null) {
|
|
1486
|
+
return '';
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
value = parseFloat(value);
|
|
1490
|
+
if (options.numFracDigits) {
|
|
1491
|
+
value = value.toFixed(options.numFracDigits); // toFixed() rounds the value, is ok?
|
|
1492
|
+
} else {
|
|
1493
|
+
// if the float has 13 digits or more (1 trillion or greater)
|
|
1494
|
+
// or the float has 7 decimals or more, use scientific notation
|
|
1495
|
+
// NOTE: javascript in browser uses 22 as the threshold for large numbers
|
|
1496
|
+
// If there are 22 digits or more, then scientific notation is used
|
|
1497
|
+
// ecmascript language spec: https://262.ecma-international.org/5.1/#sec-9.8.1
|
|
1498
|
+
if (Math.abs(value) >= 1000000000000 || Math.abs(value) < 0.000001) {
|
|
1499
|
+
// this also ensures there are more digits than the precision used
|
|
1500
|
+
// so the number will be converted to scientific notation instead of
|
|
1501
|
+
// being padded with zeroes with no conversion
|
|
1502
|
+
// for example: 0.000001.toPrecision(4) ==> '0.000001000'
|
|
1503
|
+
value = value.toPrecision(5);
|
|
1504
|
+
} else {
|
|
1505
|
+
value = value.toFixed(4);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// Remove leading zeroes
|
|
1511
|
+
value = value.toString().replace(/^0+(?!\.|$)/, '');
|
|
1512
|
+
|
|
1513
|
+
// Add comma separators
|
|
1514
|
+
var parts = value.split(".");
|
|
1515
|
+
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
1516
|
+
return parts.join(".");
|
|
1517
|
+
},
|
|
1518
|
+
|
|
1519
|
+
/**
|
|
1520
|
+
* @function
|
|
1521
|
+
* @param {Object} value A text value to transform
|
|
1522
|
+
* @param {Object} [options] Configuration options.
|
|
1523
|
+
* @return {string} A string representation of value
|
|
1524
|
+
* @desc Formats a given text value into a string for display.
|
|
1525
|
+
*/
|
|
1526
|
+
printText: function printText(value, options) {
|
|
1527
|
+
options = (typeof options === 'undefined') ? {} : options;
|
|
1528
|
+
if (value === null) {
|
|
1529
|
+
return '';
|
|
1530
|
+
}
|
|
1531
|
+
if (typeof value === 'object') {
|
|
1532
|
+
return JSON.stringify(value);
|
|
1533
|
+
}
|
|
1534
|
+
return value.toString();
|
|
1535
|
+
},
|
|
1536
|
+
|
|
1537
|
+
/**
|
|
1538
|
+
* @function
|
|
1539
|
+
* @param {Object} value The Markdown to transform
|
|
1540
|
+
* @param {Object} [options] Configuration options.
|
|
1541
|
+
* @return {string} A string representation of value
|
|
1542
|
+
* @desc Formats Markdown syntax into an HTML string for display.
|
|
1543
|
+
*/
|
|
1544
|
+
printMarkdown: function printMarkdown(value, options) {
|
|
1545
|
+
options = (typeof options === 'undefined') ? {} : options;
|
|
1546
|
+
if (value === null) {
|
|
1547
|
+
return '';
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
return renderMarkdown(value, options.inline);
|
|
1551
|
+
},
|
|
1552
|
+
|
|
1553
|
+
/**
|
|
1554
|
+
* @function
|
|
1555
|
+
* @param {Object} value A json value to transform
|
|
1556
|
+
* @return {string} A string representation of value based on different context
|
|
1557
|
+
* The beautified version of JSON in other cases
|
|
1558
|
+
* A special case to show null if the value is blank string
|
|
1559
|
+
* @desc Formats a given json value into a string for display.
|
|
1560
|
+
*/
|
|
1561
|
+
printJSON: function printJSON(value, options) {
|
|
1562
|
+
return value === "" ? JSON.stringify(null) : JSON.stringify(value, undefined, 2);
|
|
1563
|
+
},
|
|
1564
|
+
|
|
1565
|
+
/**
|
|
1566
|
+
* @function
|
|
1567
|
+
* @param {string} value The gene sequence to transform
|
|
1568
|
+
* @param {Object} [options] Configuration options. Accepted parameters
|
|
1569
|
+
* are "increment" (desired number of characters in each segment) and
|
|
1570
|
+
* "separator" (desired separator between segments).
|
|
1571
|
+
* @return {string} A string representation of value
|
|
1572
|
+
* @desc Formats a gene sequence into a string for display. By default,
|
|
1573
|
+
* it will split gene sequence into an increment of 10 characters and
|
|
1574
|
+
* insert an empty space in between each increment.
|
|
1575
|
+
*/
|
|
1576
|
+
|
|
1577
|
+
printGeneSeq: function printGeneSeq(value, options) {
|
|
1578
|
+
options = (typeof options === 'undefined') ? {} : options;
|
|
1579
|
+
|
|
1580
|
+
if (value === null) {
|
|
1581
|
+
return '';
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
try {
|
|
1585
|
+
// Default separator is a space.
|
|
1586
|
+
if (!options.separator) {
|
|
1587
|
+
options.separator = ' ';
|
|
1588
|
+
}
|
|
1589
|
+
// Default increment is 10
|
|
1590
|
+
if (!options.increment) {
|
|
1591
|
+
options.increment = 10;
|
|
1592
|
+
}
|
|
1593
|
+
var inc = parseInt(options.increment, 10);
|
|
1594
|
+
|
|
1595
|
+
if (inc === 0) {
|
|
1596
|
+
return value.toString();
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// Reset the increment if it's negative
|
|
1600
|
+
if (inc <= -1) {
|
|
1601
|
+
inc = 1;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
var formattedSeq = '`';
|
|
1605
|
+
var separator = options.separator;
|
|
1606
|
+
while (value.length >= inc) {
|
|
1607
|
+
// Get the first inc number of chars
|
|
1608
|
+
var chunk = value.slice(0, inc);
|
|
1609
|
+
// Append the chunk and separator
|
|
1610
|
+
formattedSeq += chunk + separator;
|
|
1611
|
+
// Remove this chunk from value
|
|
1612
|
+
value = value.slice(inc);
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// Append any remaining chars from value that was too small to form an increment
|
|
1616
|
+
formattedSeq += value;
|
|
1617
|
+
|
|
1618
|
+
// Slice off separator at the end
|
|
1619
|
+
if (formattedSeq.slice(-1) == separator) {
|
|
1620
|
+
formattedSeq = formattedSeq.slice(0, -1);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// Add the ending backtick at the end
|
|
1624
|
+
formattedSeq += '`';
|
|
1625
|
+
|
|
1626
|
+
// Run it through renderMarkdown to get the sequence in a fixed-width font
|
|
1627
|
+
return renderMarkdown(formattedSeq, true);
|
|
1628
|
+
} catch (e) {
|
|
1629
|
+
$log.error("Couldn't parse the given markdown value: " + value);
|
|
1630
|
+
$log.error(e);
|
|
1631
|
+
return value;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
},
|
|
1635
|
+
|
|
1636
|
+
/**
|
|
1637
|
+
* @function
|
|
1638
|
+
* @param {Array} value the array of values
|
|
1639
|
+
* @param {Object} options Configuration options. Accepted parameters:
|
|
1640
|
+
* - `isMarkdown`: if this is true, we will not esacpe markdown characters
|
|
1641
|
+
* - `returnArray`: if this is true, it will return an array of strings.
|
|
1642
|
+
* @return {string|string[]} A string represntation of array.
|
|
1643
|
+
* @desc
|
|
1644
|
+
* Will generate a comma seperated value for an array. It will also change `null` and `""`
|
|
1645
|
+
* to their special presentation.
|
|
1646
|
+
* The returned value might return markdown, which then should call printMarkdown on it.
|
|
1647
|
+
*/
|
|
1648
|
+
printArray: function (value, options) {
|
|
1649
|
+
options = (typeof options === 'undefined') ? {} : options;
|
|
1650
|
+
|
|
1651
|
+
if (!value || !Array.isArray(value) || value.length === 0) {
|
|
1652
|
+
return '';
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
var arr = value.map(function (v) {
|
|
1656
|
+
var isMarkdown = (options.isMarkdown === true);
|
|
1657
|
+
var pv = v;
|
|
1658
|
+
if (v === "") {
|
|
1659
|
+
pv = _specialPresentation.EMPTY_STR;
|
|
1660
|
+
isMarkdown = true;
|
|
1661
|
+
}
|
|
1662
|
+
else if (v == null) {
|
|
1663
|
+
pv = _specialPresentation.NULL;
|
|
1664
|
+
isMarkdown = true;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
if (!isMarkdown) pv = _escapeMarkdownCharacters(pv);
|
|
1668
|
+
return pv;
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
if (options.returnArray) return arr;
|
|
1672
|
+
return arr.join(", ");
|
|
1673
|
+
},
|
|
1674
|
+
|
|
1675
|
+
printColor: function (value, options) {
|
|
1676
|
+
options = (typeof options === 'undefined') ? {} : options;
|
|
1677
|
+
|
|
1678
|
+
if (!isValidColorRGBHex(value)) {
|
|
1679
|
+
return '';
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
value = value.toUpperCase();
|
|
1683
|
+
return ':span: :/span:{.' + _classNames.colorPreview + ' style=background-color:' + value +'} ' + value;
|
|
1684
|
+
},
|
|
1685
|
+
|
|
1686
|
+
/**
|
|
1687
|
+
* Return the humanize value of byte count
|
|
1688
|
+
*
|
|
1689
|
+
* This function will not round up and will only truncate the number
|
|
1690
|
+
* to honor the given precision. In 'si', precision below 3 is not allowed.
|
|
1691
|
+
* Similarly, precision below 4 is not allowed in 'binary'.
|
|
1692
|
+
* 'raw' will return the "formatted" value.
|
|
1693
|
+
*
|
|
1694
|
+
* @param {*} value
|
|
1695
|
+
* @param {?string} mode either `raw`, `si`, or `binary` (if invalid or missing, 'si' will be used)
|
|
1696
|
+
* @param {?number} precision An integer specifying the number of digits to be displayed
|
|
1697
|
+
* (if invalid or missing, `3` will be used by default.)
|
|
1698
|
+
* @param {?boolean} withTooltip whether we should return it with tooltip or just the value.
|
|
1699
|
+
*/
|
|
1700
|
+
humanizeBytes: function (value, mode, precision, withTooltip) {
|
|
1701
|
+
// we cannot use parseInt here since it won't allow larger numbers.
|
|
1702
|
+
var v = parseFloat(value);
|
|
1703
|
+
mode = ['raw', 'si', 'binary'].indexOf(mode) === -1 ? 'si' : mode;
|
|
1704
|
+
|
|
1705
|
+
if (isNaN(v)) return '';
|
|
1706
|
+
if (v === 0 || mode === 'raw') {
|
|
1707
|
+
return _formatUtils.printInteger(value);
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
var divisor = 1000, units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
|
1711
|
+
if (mode === 'binary') {
|
|
1712
|
+
divisor = 1024;
|
|
1713
|
+
units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// find the closest power of the divisor to the given number ('u').
|
|
1717
|
+
// in the end, 'v' will be the number that we should display.
|
|
1718
|
+
var u = 0;
|
|
1719
|
+
while (v >= divisor || -v >= divisor) {
|
|
1720
|
+
v /= divisor;
|
|
1721
|
+
u++;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
// our units don't support this, so just return the "raw" mode value.
|
|
1725
|
+
if (u >= units.length) {
|
|
1726
|
+
return _formatUtils.printInteger(value);
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
// we don't want to truncate the value, so we should set a minimum
|
|
1730
|
+
var minP = mode === "si" ? 3 : 4;
|
|
1731
|
+
|
|
1732
|
+
var res = (u ? _toPrecision(v, precision, minP) : v) + ' ' + units[u];
|
|
1733
|
+
if (typeof withTooltip === 'boolean' && withTooltip && u > 0) {
|
|
1734
|
+
var numBytes = _formatUtils.printInteger(Math.pow(divisor, u));
|
|
1735
|
+
var tooltip = _formatUtils.printInteger(value);
|
|
1736
|
+
tooltip += ' bytes (1 ' + units[u] + ' = ' + numBytes + ' bytes)';
|
|
1737
|
+
res = ':span:' + res + ':/span:{data-chaise-tooltip="' + tooltip + '"}';
|
|
1738
|
+
}
|
|
1739
|
+
return res;
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
|
|
1743
|
+
/**
|
|
1744
|
+
* format the raw value based on the column definition type, heuristics, annotations, etc.
|
|
1745
|
+
* @param {Type} type - the type object of the column
|
|
1746
|
+
* @param {Object} data - the 'raw' data value.
|
|
1747
|
+
* @returns {string} The formatted value.
|
|
1748
|
+
*/
|
|
1749
|
+
export function _formatValueByType(type, data, options) {
|
|
1750
|
+
var utils = _formatUtils;
|
|
1751
|
+
switch(type.name) {
|
|
1752
|
+
case 'timestamp':
|
|
1753
|
+
case 'timestamptz':
|
|
1754
|
+
data = utils.printTimestamp(data, options);
|
|
1755
|
+
break;
|
|
1756
|
+
case 'date':
|
|
1757
|
+
data = utils.printDate(data, options);
|
|
1758
|
+
break;
|
|
1759
|
+
case 'numeric':
|
|
1760
|
+
case 'float4':
|
|
1761
|
+
case 'float8':
|
|
1762
|
+
data = utils.printFloat(data, options);
|
|
1763
|
+
break;
|
|
1764
|
+
case 'int2':
|
|
1765
|
+
case 'int4':
|
|
1766
|
+
case 'int8':
|
|
1767
|
+
data = utils.printInteger(data, options);
|
|
1768
|
+
break;
|
|
1769
|
+
case 'boolean':
|
|
1770
|
+
data = utils.printBoolean(data, options);
|
|
1771
|
+
break;
|
|
1772
|
+
case 'markdown':
|
|
1773
|
+
// Do nothing as we will format markdown at the end of format
|
|
1774
|
+
data = data.toString();
|
|
1775
|
+
break;
|
|
1776
|
+
case 'gene_sequence':
|
|
1777
|
+
data = utils.printGeneSeq(data, options);
|
|
1778
|
+
break;
|
|
1779
|
+
//Cases to support json and jsonb columns
|
|
1780
|
+
case 'json':
|
|
1781
|
+
case 'jsonb':
|
|
1782
|
+
data = utils.printJSON(data, options);
|
|
1783
|
+
break;
|
|
1784
|
+
case 'color_rgb_hex':
|
|
1785
|
+
data = utils.printColor(data, options);
|
|
1786
|
+
break;
|
|
1787
|
+
default: // includes 'text' and 'longtext' cases
|
|
1788
|
+
data = type.baseType ? _formatValueByType(type.baseType, data, options) : utils.printText(data, options);
|
|
1789
|
+
break;
|
|
1790
|
+
}
|
|
1791
|
+
return data;
|
|
1792
|
+
};
|
|
1793
|
+
|
|
1794
|
+
export function _isValidSortElement(element, index, array) {
|
|
1795
|
+
return (typeof element == 'object' &&
|
|
1796
|
+
typeof element.column == 'string' &&
|
|
1797
|
+
typeof element.descending == 'boolean');
|
|
1798
|
+
};
|
|
1799
|
+
|
|
1800
|
+
/**
|
|
1801
|
+
* @desc
|
|
1802
|
+
* Given a url and origin, test whether the url has the host.
|
|
1803
|
+
* Returns `null` if we cannot determine the origin.
|
|
1804
|
+
*
|
|
1805
|
+
* @function
|
|
1806
|
+
* @private
|
|
1807
|
+
* @param {string} url url string
|
|
1808
|
+
* @return {boolean|null}
|
|
1809
|
+
*/
|
|
1810
|
+
export function _isSameHost(url) {
|
|
1811
|
+
// chaise-config internalHosts are not defined, so we cannot determine
|
|
1812
|
+
const _clientConfig = ConfigService.clientConfig;
|
|
1813
|
+
if (!isObjectAndNotNull(_clientConfig) || _clientConfig.internalHosts.length == 0) return null;
|
|
1814
|
+
|
|
1815
|
+
var hasProtocol = new RegExp('^(?:[a-z]+:)?//', 'i').test(url);
|
|
1816
|
+
|
|
1817
|
+
// if the url doesn't have origin (relative)
|
|
1818
|
+
if (!hasProtocol) return true;
|
|
1819
|
+
|
|
1820
|
+
var urlParts = url.split("/");
|
|
1821
|
+
|
|
1822
|
+
// invalid url format: cannot determine the origin
|
|
1823
|
+
if (urlParts.length < 3) return null;
|
|
1824
|
+
|
|
1825
|
+
// actual comparission of the origin
|
|
1826
|
+
return _clientConfig.internalHosts.some(function (host) {
|
|
1827
|
+
return typeof host === "string" && host.length > 0 && urlParts[2].indexOf(host) === 0;
|
|
1828
|
+
});
|
|
1829
|
+
};
|
|
1830
|
+
|
|
1831
|
+
// Characters to replace Markdown special characters
|
|
1832
|
+
const _escapeReplacementsForMarkdown = [
|
|
1833
|
+
[ /\*/g, '\\*' ],
|
|
1834
|
+
[ /#/g, '\\#' ],
|
|
1835
|
+
[ /\//g, '\\/' ],
|
|
1836
|
+
[ /\(/g, '\\(' ],
|
|
1837
|
+
[ /\)/g, '\\)' ],
|
|
1838
|
+
[ /\[/g, '\\[' ],
|
|
1839
|
+
[ /\]/g, '\\]' ],
|
|
1840
|
+
[ /\{/g, '\\{' ],
|
|
1841
|
+
[ /\}/g, '\\}' ],
|
|
1842
|
+
[ new RegExp("\<","g"), '<' ],
|
|
1843
|
+
[ new RegExp("\>","g"), '>' ],
|
|
1844
|
+
[ /_/g, '\\_' ],
|
|
1845
|
+
[ /\!/g, '\\!' ],
|
|
1846
|
+
[ /\./g, '\\.' ],
|
|
1847
|
+
[ /\+/g, '\\+' ],
|
|
1848
|
+
[ /\-/g, '\\-' ],
|
|
1849
|
+
[ /\`/g, '\\`' ]];
|
|
1850
|
+
|
|
1851
|
+
/**
|
|
1852
|
+
* @function
|
|
1853
|
+
* @param {String} text The text in which escaping needs to happen.
|
|
1854
|
+
* @desc
|
|
1855
|
+
* This private utility function escapes markdown special characters
|
|
1856
|
+
* It is used with Mustache to escape value of variables that have markdown characters in them
|
|
1857
|
+
* @returns {String} String after escaping
|
|
1858
|
+
*/
|
|
1859
|
+
export function _escapeMarkdownCharacters(text) {
|
|
1860
|
+
return _escapeReplacementsForMarkdown.reduce(
|
|
1861
|
+
function(text, replacement) {
|
|
1862
|
+
return text.replace(replacement[0], replacement[1]);
|
|
1863
|
+
}, text);
|
|
1864
|
+
};
|
|
1865
|
+
|
|
1866
|
+
/**
|
|
1867
|
+
* @function
|
|
1868
|
+
* @desc
|
|
1869
|
+
* A function used by Mustache to encode strings in a template
|
|
1870
|
+
* @return {Function} A function that is called by Mustache when it stumbles across
|
|
1871
|
+
* {{#encode}} string while parsing the template.
|
|
1872
|
+
*/
|
|
1873
|
+
export function _encodeForMustacheTemplate() {
|
|
1874
|
+
return function(text, render) {
|
|
1875
|
+
return fixedEncodeURIComponent(render(text));
|
|
1876
|
+
};
|
|
1877
|
+
};
|
|
1878
|
+
|
|
1879
|
+
/**
|
|
1880
|
+
* @function
|
|
1881
|
+
* @desc
|
|
1882
|
+
* A function used by Mustache to escape Markdown characters in a string
|
|
1883
|
+
* @return {Function} A function that is called by Mustache when it stumbles across
|
|
1884
|
+
* {{#escape}} string while parsing the template.
|
|
1885
|
+
*/
|
|
1886
|
+
export function _escapeForMustacheTemplate() {
|
|
1887
|
+
return function(text, render) {
|
|
1888
|
+
return _escapeMarkdownCharacters(render(text));
|
|
1889
|
+
};
|
|
1890
|
+
};
|
|
1891
|
+
|
|
1892
|
+
/**
|
|
1893
|
+
* @function
|
|
1894
|
+
* @desc
|
|
1895
|
+
* Gets currDate object once the page loads for future access in templates
|
|
1896
|
+
* @return {Object} A date object that contains all properties
|
|
1897
|
+
*/
|
|
1898
|
+
const getCurrDate = function() {
|
|
1899
|
+
var date = new Date();
|
|
1900
|
+
|
|
1901
|
+
var dateObj = {};
|
|
1902
|
+
|
|
1903
|
+
// Set date properties
|
|
1904
|
+
dateObj.day = date.getDay();
|
|
1905
|
+
dateObj.date = date.getDate();
|
|
1906
|
+
dateObj.month = date.getMonth() + 1;
|
|
1907
|
+
dateObj.year = date.getFullYear();
|
|
1908
|
+
dateObj.dateString = date.toDateString();
|
|
1909
|
+
|
|
1910
|
+
// Set Time porperties
|
|
1911
|
+
dateObj.hours = date.getHours();
|
|
1912
|
+
dateObj.minutes = date.getMinutes();
|
|
1913
|
+
dateObj.seconds = date.getSeconds();
|
|
1914
|
+
dateObj.milliseconds = date.getMilliseconds();
|
|
1915
|
+
dateObj.timestamp = date.getTime();
|
|
1916
|
+
dateObj.timeString = date.toTimeString();
|
|
1917
|
+
|
|
1918
|
+
dateObj.ISOString = date.toISOString();
|
|
1919
|
+
dateObj.GMTString = date.toGMTString();
|
|
1920
|
+
dateObj.UTCString = date.toUTCString();
|
|
1921
|
+
|
|
1922
|
+
dateObj.localeDateString = date.toLocaleDateString();
|
|
1923
|
+
dateObj.localeTimeString = date.toLocaleTimeString();
|
|
1924
|
+
dateObj.localeString = date.toLocaleString();
|
|
1925
|
+
|
|
1926
|
+
return dateObj;
|
|
1927
|
+
};
|
|
1928
|
+
export const _currDate = getCurrDate();
|
|
1929
|
+
|
|
1930
|
+
/**
|
|
1931
|
+
* @function
|
|
1932
|
+
* @desc
|
|
1933
|
+
* Add utility objects such as date (Computed value) to mustache data obj
|
|
1934
|
+
* so that they can be accessed in the template
|
|
1935
|
+
*/
|
|
1936
|
+
export function _addErmrestVarsToTemplate(obj, catalog) {
|
|
1937
|
+
|
|
1938
|
+
// date object
|
|
1939
|
+
obj.$moment = _currDate;
|
|
1940
|
+
|
|
1941
|
+
// if there is a window object, we are in the browser
|
|
1942
|
+
if (!ENV_IS_NODE && typeof window === 'object' && window.location) {
|
|
1943
|
+
var chaiseBasePath = '/chaise/';
|
|
1944
|
+
if (isObjectAndNotNull(window.chaiseBuildVariables)) {
|
|
1945
|
+
// new version
|
|
1946
|
+
if (isStringAndNotEmpty(window.chaiseBuildVariables.CHAISE_BASE_PATH)) {
|
|
1947
|
+
chaiseBasePath = window.chaiseBuildVariables.CHAISE_BASE_PATH;
|
|
1948
|
+
}
|
|
1949
|
+
// angularj version
|
|
1950
|
+
else if (isStringAndNotEmpty(window.chaiseBuildVariables.chaiseBasePath)) {
|
|
1951
|
+
chaiseBasePath = window.chaiseBuildVariables.chaiseBasePath;
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
obj.$location = {
|
|
1956
|
+
origin: window.location.origin,
|
|
1957
|
+
host: window.location.host,
|
|
1958
|
+
hostname: window.location.hostname,
|
|
1959
|
+
chaise_path: chaiseBasePath
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
if (catalog) {
|
|
1964
|
+
|
|
1965
|
+
if (catalog.server) {
|
|
1966
|
+
// deriva-client-context
|
|
1967
|
+
obj.$dcctx = {
|
|
1968
|
+
cid: catalog.server.cid,
|
|
1969
|
+
pid: catalog.server.pid
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
var catalogSnapshot = catalog.id.split('@');
|
|
1974
|
+
obj.$catalog = {
|
|
1975
|
+
snapshot: catalog.id,
|
|
1976
|
+
id: catalogSnapshot[0]
|
|
1977
|
+
};
|
|
1978
|
+
|
|
1979
|
+
if (catalogSnapshot.length === 2) obj.$catalog.version = catalogSnapshot[1];
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
if (AuthnService.session) {
|
|
1983
|
+
var session = AuthnService.session;
|
|
1984
|
+
|
|
1985
|
+
obj.$session = {};
|
|
1986
|
+
Object.keys(session).forEach(function (key) {
|
|
1987
|
+
obj.$session[key] = session[key];
|
|
1988
|
+
});
|
|
1989
|
+
|
|
1990
|
+
// If extensions is present, put all dbgap permissions into a map
|
|
1991
|
+
// NOTE: not sure if we want to check for `has_ras_permissions` too or not since if that is true, it means ras_dbgap_permissions is also defined
|
|
1992
|
+
// if it's false, the array won't be defined
|
|
1993
|
+
if (session.client.extensions && session.client.extensions.ras_dbgap_permissions && Array.isArray(session.client.extensions.ras_dbgap_permissions)) {
|
|
1994
|
+
var map = {};
|
|
1995
|
+
session.client.extensions.ras_dbgap_permissions.forEach(function (perm) {
|
|
1996
|
+
if (typeof perm === "object" && perm.phs_id) map[perm.phs_id] = true;
|
|
1997
|
+
});
|
|
1998
|
+
|
|
1999
|
+
obj.$session.client.extensions.ras_dbgap_phs_ids = map;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
/**
|
|
2004
|
+
* TODO if we want ot add dynamic variables to the template,
|
|
2005
|
+
* we could add "site_var_queries" or "site_var_sources" (we have to decide which one to use).
|
|
2006
|
+
* We didn't completely fleshed out what this property looks like, but it should be similar to "source object"
|
|
2007
|
+
* where you can define a path and project list.
|
|
2008
|
+
*/
|
|
2009
|
+
const cc = ConfigService.clientConfig;
|
|
2010
|
+
if (isObjectAndNotNull(cc) && isObjectAndNotNull(cc.templating) && isObjectAndNotNull(cc.templating.site_var)) {
|
|
2011
|
+
obj.$site_var = cc.templating.site_var;
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
|
|
2015
|
+
/**
|
|
2016
|
+
* @function
|
|
2017
|
+
* @desc
|
|
2018
|
+
* Replace variables having dot with underscore so that they can be accessed in the template
|
|
2019
|
+
* @param {Object} keyValues The key-value pair of object.
|
|
2020
|
+
* @param {Object} options An object of options which might contain additional functions to be injected
|
|
2021
|
+
*
|
|
2022
|
+
* @return {Object} obj
|
|
2023
|
+
*/
|
|
2024
|
+
export function _addTemplateVars(keyValues, catalog, options) {
|
|
2025
|
+
|
|
2026
|
+
var obj = {};
|
|
2027
|
+
if (keyValues && isObject(keyValues)) {
|
|
2028
|
+
try {
|
|
2029
|
+
// recursively replace dot with underscore in column names.
|
|
2030
|
+
obj = _replaceDotWithUnderscore(keyValues);
|
|
2031
|
+
} catch (err) {
|
|
2032
|
+
// This should not happen since we're guarding against custom type objects.
|
|
2033
|
+
obj = keyValues;
|
|
2034
|
+
$log.error("Could not process the given keyValues in _renderTemplate. Ignoring the _replaceDotWithUnderscore logic.");
|
|
2035
|
+
$log.error(err);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
// Inject ermrest internal utility objects such as date
|
|
2040
|
+
_addErmrestVarsToTemplate(obj, catalog);
|
|
2041
|
+
|
|
2042
|
+
// Inject other functions provided in the options.functions array if needed
|
|
2043
|
+
if (options.functions && options.functions.length) {
|
|
2044
|
+
options.functions.forEach(function(f) {
|
|
2045
|
+
obj[f.name] = function() {
|
|
2046
|
+
return f.fn;
|
|
2047
|
+
};
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
return obj;
|
|
2052
|
+
};
|
|
2053
|
+
|
|
2054
|
+
/*
|
|
2055
|
+
* @function
|
|
2056
|
+
* @private
|
|
2057
|
+
* @param {String} template The template string to transform
|
|
2058
|
+
* @param {Object} obj The key-value pair of object to be used for template tags replacement.
|
|
2059
|
+
* @param {Object} [options] Configuration options.
|
|
2060
|
+
* @return {string} A string produced after templating
|
|
2061
|
+
* @desc Returns a string produced as a result of templating using `Mustache`.
|
|
2062
|
+
*/
|
|
2063
|
+
export function renderMustacheTemplate(template, keyValues, catalog, options) {
|
|
2064
|
+
|
|
2065
|
+
options = options || {};
|
|
2066
|
+
|
|
2067
|
+
var obj = _addTemplateVars(keyValues, catalog, options), content;
|
|
2068
|
+
|
|
2069
|
+
// Inject the encode function in the obj object
|
|
2070
|
+
obj.encode = _encodeForMustacheTemplate;
|
|
2071
|
+
|
|
2072
|
+
// Inject the escape function in the obj object
|
|
2073
|
+
obj.escape = _escapeForMustacheTemplate;
|
|
2074
|
+
|
|
2075
|
+
// If we should validate, validate the template and if returns false, return null.
|
|
2076
|
+
if (!options.avoidValidation && !_validateMustacheTemplate(template, obj, catalog)) {
|
|
2077
|
+
return null;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
try {
|
|
2081
|
+
content = mustache.render(template, obj);
|
|
2082
|
+
} catch(e) {
|
|
2083
|
+
$log.error(e);
|
|
2084
|
+
content = null;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
return content;
|
|
2088
|
+
};
|
|
2089
|
+
|
|
2090
|
+
/**
|
|
2091
|
+
* Returns true if all the used keys have values.
|
|
2092
|
+
*
|
|
2093
|
+
* NOTE:
|
|
2094
|
+
* This implementation is very limited and if conditional Mustache statements
|
|
2095
|
+
* of the form {{#var}}{{/var}} or {{^var}}{{/var}} found then it won't check
|
|
2096
|
+
* for null values and will return true.
|
|
2097
|
+
*
|
|
2098
|
+
* @param {string} template mustache template
|
|
2099
|
+
* @param {object} keyValues key-value pairs
|
|
2100
|
+
* @param {Array.<string>=} ignoredColumns the columns that should be ignored (optional)
|
|
2101
|
+
* @return {boolean} true if all the used keys have values
|
|
2102
|
+
*/
|
|
2103
|
+
export function _validateMustacheTemplate(template, keyValues, catalog, ignoredColumns) {
|
|
2104
|
+
|
|
2105
|
+
// Inject ermrest internal utility objects such as date
|
|
2106
|
+
// needs to be done in the case _validateTemplate is called without first calling _renderTemplate
|
|
2107
|
+
_addErmrestVarsToTemplate(keyValues, catalog);
|
|
2108
|
+
|
|
2109
|
+
var conditionalRegex = /\{\{(#|\^)([^\{\}]+)\}\}/, i, key, value;
|
|
2110
|
+
|
|
2111
|
+
// If no conditional Mustache statements of the form {{#var}}{{/var}} or {{^var}}{{/var}} not found then do direct null check
|
|
2112
|
+
if (!conditionalRegex.exec(template)) {
|
|
2113
|
+
|
|
2114
|
+
// Grab all placeholders ({{PROP_NAME}}) in the template
|
|
2115
|
+
var placeholders = template.match(/\{\{([^\{\}]+)\}\}/ig);
|
|
2116
|
+
|
|
2117
|
+
// If there are any placeholders
|
|
2118
|
+
if (placeholders && placeholders.length) {
|
|
2119
|
+
|
|
2120
|
+
// Get unique placeholders
|
|
2121
|
+
placeholders = placeholders.filter(function(item, i, ar) { return ar.indexOf(item) === i; });
|
|
2122
|
+
|
|
2123
|
+
/*
|
|
2124
|
+
* Iterate over all placeholders to set pattern as null if any of the
|
|
2125
|
+
* values turn out to be null or undefined
|
|
2126
|
+
*/
|
|
2127
|
+
for (i=0; i<placeholders.length;i++) {
|
|
2128
|
+
|
|
2129
|
+
// Grab actual key from the placeholder {{name}} = name, remove "{{" and "}}" from the string for key
|
|
2130
|
+
key = placeholders[i].substring(2, placeholders[i].length - 2);
|
|
2131
|
+
|
|
2132
|
+
if (key[0] == "{") key = key.substring(1, key.length -1);
|
|
2133
|
+
|
|
2134
|
+
// find the value.
|
|
2135
|
+
value = _getPath(keyValues, key.trim());
|
|
2136
|
+
|
|
2137
|
+
// TODO since we're not going inside the object this logic of ignoredColumns is not needed anymore,
|
|
2138
|
+
// it was a hack that was added for asset columns.
|
|
2139
|
+
// If key is not in ingored columns value for the key is null or undefined then return null
|
|
2140
|
+
if ((!Array.isArray(ignoredColumns) || ignoredColumns.indexOf(key) == -1) && (value === null || value === undefined)) {
|
|
2141
|
+
return false;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
return true;
|
|
2147
|
+
};
|
|
2148
|
+
|
|
2149
|
+
/**
|
|
2150
|
+
* given a string, if it's a valid template_engine use it,
|
|
2151
|
+
* otherwise get it from the client config.
|
|
2152
|
+
* @param {string} engine
|
|
2153
|
+
*/
|
|
2154
|
+
export function _getTemplateEngine(engine) {
|
|
2155
|
+
var isValid = function (val) {
|
|
2156
|
+
return isStringAndNotEmpty(val) && Object.values(TEMPLATE_ENGINES).indexOf(val) !== -1;
|
|
2157
|
+
};
|
|
2158
|
+
if (isValid(engine)) {
|
|
2159
|
+
return engine;
|
|
2160
|
+
}
|
|
2161
|
+
const _clientConfig = ConfigService.clientConfig;
|
|
2162
|
+
if (isObjectAndNotNull(_clientConfig) && isObjectAndNotNull(_clientConfig.templating) &&
|
|
2163
|
+
isValid(_clientConfig.templating.engine)) {
|
|
2164
|
+
return _clientConfig.templating.engine;
|
|
2165
|
+
}
|
|
2166
|
+
return TEMPLATE_ENGINES.MUSTACHE;
|
|
2167
|
+
};
|
|
2168
|
+
|
|
2169
|
+
/**
|
|
2170
|
+
* A wrapper for {renderMustacheTemplate}
|
|
2171
|
+
* acceptable options:
|
|
2172
|
+
* - templateEngine: "mustache" or "handlbars"
|
|
2173
|
+
* - avoidValidation: to avoid validation of the template
|
|
2174
|
+
* - allowObject: if the returned string is a parsable object, return it as object
|
|
2175
|
+
* instead of string.
|
|
2176
|
+
*
|
|
2177
|
+
* @param {string} template - template to be rendered
|
|
2178
|
+
* @param {object} keyValues - formatted key value pairs needed for the template
|
|
2179
|
+
* @param {Catalog} catalog - the catalog that this value is for
|
|
2180
|
+
* @param {any=} options optioanl parameters
|
|
2181
|
+
* @return {string} Returns a string produced as a result of templating using options.templateEngine or `Mustache` by default.
|
|
2182
|
+
*/
|
|
2183
|
+
export function _renderTemplate(template, keyValues, catalog, options) {
|
|
2184
|
+
|
|
2185
|
+
if (typeof template !== 'string') return null;
|
|
2186
|
+
|
|
2187
|
+
options = options || {};
|
|
2188
|
+
|
|
2189
|
+
var res, objRes;
|
|
2190
|
+
if (_getTemplateEngine(options.templateEngine) === TEMPLATE_ENGINES.HANDLEBARS) {
|
|
2191
|
+
// render the template using Handlebars
|
|
2192
|
+
res = HandlebarsService.render(template, keyValues, catalog, options);
|
|
2193
|
+
} else {
|
|
2194
|
+
// render the template using Mustache
|
|
2195
|
+
res = renderMustacheTemplate(template, keyValues, catalog, options);
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
if (options.allowObject) {
|
|
2199
|
+
try {
|
|
2200
|
+
// if it can be parsed and is an object, return the object
|
|
2201
|
+
objRes = JSON.parse(res);
|
|
2202
|
+
if (typeof objRes === "object") {
|
|
2203
|
+
return objRes;
|
|
2204
|
+
}
|
|
2205
|
+
} catch {
|
|
2206
|
+
// ignore
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
return res;
|
|
2211
|
+
};
|
|
2212
|
+
|
|
2213
|
+
/**
|
|
2214
|
+
* A wrapper for {_validateMustacheTemplate}
|
|
2215
|
+
* it will take care of adding formmatted and unformatted values.
|
|
2216
|
+
* options.ignoredColumns: list of columns that you want validator to ignore
|
|
2217
|
+
* options.templateEngine: "mustache" or "handlbars"
|
|
2218
|
+
*
|
|
2219
|
+
* @param {Table} table
|
|
2220
|
+
* @param {object} data
|
|
2221
|
+
* @param {string} template
|
|
2222
|
+
* @param {Catalog} catalog
|
|
2223
|
+
* @param {Array.<string>=} ignoredColumns the columns that should be ignored (optional)
|
|
2224
|
+
* @return {boolean} True if the template is valid.
|
|
2225
|
+
*/
|
|
2226
|
+
export function _validateTemplate(template, data, catalog, options) {
|
|
2227
|
+
|
|
2228
|
+
var ignoredColumns;
|
|
2229
|
+
if (options !== undefined && Array.isArray(options.ignoredColumns)) {
|
|
2230
|
+
ignoredColumns = options.ignoredColumns;
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
if (_getTemplateEngine(options ? options.templateEngine : '') === TEMPLATE_ENGINES.HANDLEBARS) {
|
|
2234
|
+
// call the actual Handlebar validator
|
|
2235
|
+
return HandlebarsService.validate(template, data, catalog, ignoredColumns)
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
// call the actual mustache validator
|
|
2239
|
+
return _validateMustacheTemplate(template, data, catalog, ignoredColumns);
|
|
2240
|
+
};
|
|
2241
|
+
|
|
2242
|
+
/**
|
|
2243
|
+
* Given a markdown_pattern template and data, will return the appropriate
|
|
2244
|
+
* presentation value.
|
|
2245
|
+
*
|
|
2246
|
+
* @param {String} template the handlebars/mustache template
|
|
2247
|
+
* @param {Object} data the key-value pair of data
|
|
2248
|
+
* @param {Table} table the table object
|
|
2249
|
+
* @param {String} context context string
|
|
2250
|
+
* @param {Object} options
|
|
2251
|
+
* @return {{isHTML: boolean, value: string, unformatted: string}} An object with `isHTML` and `value` attributes.
|
|
2252
|
+
* @memberof ERMrest
|
|
2253
|
+
* @function processMarkdownPattern
|
|
2254
|
+
*/
|
|
2255
|
+
export function processMarkdownPattern(template, data, table, context, options) {
|
|
2256
|
+
var res = _renderTemplate(template, data, table ? table.schema.catalog : null, options);
|
|
2257
|
+
|
|
2258
|
+
if (res === null || res.trim() === '') {
|
|
2259
|
+
res = table ? table._getNullValue(context) : "";
|
|
2260
|
+
return {isHTML: false, value: res, unformatted: res};
|
|
2261
|
+
}
|
|
2262
|
+
var isInline = options && options.isInline ? true : false;
|
|
2263
|
+
return {isHTML: true, value: renderMarkdown(res, isInline), unformatted: res};
|
|
2264
|
+
};
|
|
2265
|
+
|
|
2266
|
+
/**
|
|
2267
|
+
* Return an object containing window.location properties ('host', 'hostname', 'hash', 'href', 'port', 'protocol', 'search').
|
|
2268
|
+
*
|
|
2269
|
+
* @private
|
|
2270
|
+
* @param {string} url URL to be parsed
|
|
2271
|
+
* @return {object} The location object
|
|
2272
|
+
*/
|
|
2273
|
+
export function _parseUrl(url) {
|
|
2274
|
+
var m = url.match(/^(([^:\/?#]+:)?(?:\/\/(([^\/?#:]*)(?::([^\/?#:]*))?)))?([^?#]*)(\?[^#]*)?(#.*)?$/),
|
|
2275
|
+
r = {
|
|
2276
|
+
hash: m[8] || "", // #asd
|
|
2277
|
+
host: m[3] || "", // localhost:257
|
|
2278
|
+
hostname: m[4] || "", // localhost
|
|
2279
|
+
href: m[0] || "", // http://localhost:257/deploy/?asd=asd#asd
|
|
2280
|
+
origin: m[1] || "", // http://localhost:257
|
|
2281
|
+
pathname: m[6] || (m[1] ? "/" : ""), // /deploy/
|
|
2282
|
+
port: m[5] || "", // 257
|
|
2283
|
+
protocol: m[2] || "", // http:
|
|
2284
|
+
search: m[7] || "" // ?asd=asd
|
|
2285
|
+
};
|
|
2286
|
+
if (r.protocol.length == 2) {
|
|
2287
|
+
r.protocol = "file:///" + r.protocol.toUpperCase();
|
|
2288
|
+
r.origin = r.protocol + "//" + r.host;
|
|
2289
|
+
}
|
|
2290
|
+
r.href = r.origin + r.pathname + r.search + r.hash;
|
|
2291
|
+
return m && r;
|
|
2292
|
+
};
|
|
2293
|
+
|
|
2294
|
+
export function _isEntryContext(context) {
|
|
2295
|
+
return _entryContexts.indexOf(context) !== -1;
|
|
2296
|
+
};
|
|
2297
|
+
|
|
2298
|
+
export function _isCompactContext(context) {
|
|
2299
|
+
return _compactContexts.indexOf(context) !== -1;
|
|
2300
|
+
};
|