PageSourceSearch

https://symbium.com/lib/js/utilities.js?v=1_20

js symbium.com collected 2026-09-26 00:48:00 UTC 30,231 bytes, 1,142 lines download raw bytes

1const STOP_EVENT_TIME = 250,
2  ROUND_DEC = 2,
3  CHILD_WINDOW_ID = "child-window",
4  CHILD_HAS_MODAL_CLASS = "has-modal";
5
6var STOP_EVENT = {};
7
8/* Type conversion */
9function numberize(s) {
10  if (s === "blank") {
11    return 0;
12  }
13  if (s === "false") {
14    return 0;
15  }
16  if (s === "true") {
17    return 1;
18  }
19  if (s === "infinity") {
20    return Infinity;
21  }
22  if (s === "neginfinity") {
23    return -Infinity;
24  }
25  if (typeof s == "string") {
26    if (!/[0-9]+/.test(s)) {
27      return NaN;
28    } else {
29      s = s.match(/(-?\.?[0-9]+\.?[0-9]*)/)[1];
30    }
31  }
32  return parseFloat(s);
33}
34
35/* HTML */
36const copy_to_clipboard = (str) => {
37  const el = document.createElement("textarea");
38  el.value = str;
39  document.body.appendChild(el);
40  el.select();
41  document.execCommand("copy");
42  document.body.removeChild(el);
43};
44
45const linkify = (text, href, options = {}) => `
46  <a class="link no-decoration ${
47    options.class ? options.class : ""
48  }" href="${href}" target="_blank" ${
49  options.extra_attributes ? options.extra_attributes : ""
50}}>${text}</a>`;
51
52/* DOM utilities */
53
54const show_flex = (elem) => elem.css("display", "flex");
55
56function isDomElem(el) {
57  return el instanceof HTMLElement || el[0] instanceof HTMLElement
58    ? true
59    : false;
60}
61
62/* Object utilities */
63
64// Validation
65const validObject = (obj) =>
66  obj && typeof obj == "object" && !jQuery.isEmptyObject(obj);
67
68// Extend Object.prototype
69Object.filter = (obj, predicate, bykey = false) =>
70  bykey
71    ? Object.keys(obj)
72        .filter((key) => predicate(key))
73        .reduce((res, key) => ((res[key] = obj[key]), res), {})
74    : Object.keys(obj)
75        .filter((key) => predicate(obj[key]))
76        .reduce((res, key) => ((res[key] = obj[key]), res), {});
77
78Object.get_key = (obj, val) => Object.keys(obj).find((key) => obj[key] === val);
79
80Object.rekey = (obj, keysMap) =>
81  Object.entries(obj).reduce((rk, [k, v]) => {
82    if (k in keysMap) rk[keysMap[k]] = v;
83    else rk[k] = v;
84    return rk;
85  }, {});
86
87Object.remove_keys = (obj, keys) => {
88  for (let k of keys) delete obj[k];
89};
90
91Object.map = (obj, mapping, bykey = false) =>
92  bykey
93    ? Object.keys(obj).reduce((m, k) => ((m[mapping(k)] = obj[k]), m), {})
94    : Object.keys(obj).reduce((m, k) => ((m[k] = mapping(obj[k])), m), {});
95
96Object.identical = (obj1,obj2) => 
97  Object.subset_of(obj1,obj2) && 
98  Object.subset_of(obj2,obj1);
99
100Object.subset_of = (obj1,obj2) => 
101  Object.entries(obj1).every( ([k,v]) => obj2[k] == v);
102
103Object.pop = function (obj, key) {
104  const value = obj[key];
105  if (key in obj) delete obj[key];
106  return value;
107};
108
109/* String / number utilities */
110
111// Rendering
112function stripquotes(x) {
113  try {
114    if (x[0] === '"' && x[x.length - 1] === '"') return x.slice(1, -1);
115    return x;
116  } catch(e) {
117    console.log(e);
118  }
119
120  return "";
121}
122
123function strip_all_quotes(x) {
124  try {
125    return x.replace(/(^['"])|(['"]$)/g, "");
126  } catch (e) {
127    console.log(e);
128  }
129
130  return "";
131}
132
133const beautify_dollars = (x,options = {}) => {
134
135  const decoration = options.no_decoration ? false : true,
136    round = options.round;
137
138  x = typeof x == "string" ? stripquotes(x).replace(/,/g, "") : x;
139  x = numberize(x);
140
141  if (round) x = Math.round(x);
142
143  x = !isNaN(x)
144    ? x.toLocaleString('en-US',{ 
145      style: "currency", 
146      currency: "USD"
147    })
148    : "";
149
150  if (x && !decoration) x = x.split("$").join("");
151
152  if (round) return x.slice(0,-3);
153
154  return x;
155}
156
157function convert_dollars_to_cents(dollars) {
158
159  if (typeof dollars == "string")
160    dollars = numberize(dollars);
161
162  return Math.round(dollars * 100);
163}
164
165
166const beautify_number = (x, include_decimal = false) => {
167  x = typeof x == "string" ? stripquotes(x).replace(/,/g, "") : x;
168  x = numberize(x);
169  x = include_decimal ? x.toFixed(include_decimal) : Math.round(x);
170  return !isNaN(x)
171    ? x.toLocaleString("en-US", {
172        useGrouping: true,
173      })
174    : "";
175};
176
177const debeautify_number = (x) => {
178  if (typeof x === "number") return x;
179  if (typeof x !== "string") return NaN;
180
181  var neg = 1;
182  if (x[0] === "-") neg = -1;
183
184  x = x.replace(/[^0-9\.]/g, "").trim();
185
186  return x.length ? Number(x) * neg : false;
187};
188
189const number_length_x = (n, x) => {
190  if (typeof n === "number") n = String(n);
191  if (n.length < x) return number_length_x(`0${n}`, x);
192  return n;
193};
194
195function format_phone_number(value) {
196  // if input value is falsy eg if the user deletes the input, then just return
197  if (!value) return value;
198
199  // clean the input for any non-digit values.
200  let phoneNumber = value.replace(/[^\d]/g, '');
201
202  // phoneNumberLength is used to know when to apply our formatting for the phone number
203  let phoneNumberLength = phoneNumber.length;
204
205  if (phoneNumberLength > 10 && phoneNumber[0] == "1") {
206    phoneNumber = phoneNumber.slice(1);
207    phoneNumberLength -= 1
208  }
209
210  // we need to return the value with no formatting if its less than four digits
211  // this is to avoid weird behavior that occurs if you  format the area code too early
212  if (phoneNumberLength < 4) return phoneNumber;
213
214  // if phoneNumberLength is greater than 4 and less the 7 we start to return
215  // the formatted number
216  if (phoneNumberLength < 7) {
217    return `(${phoneNumber.slice(0, 3)}) ${phoneNumber.slice(3)}`;
218  }
219
220  // finally, if the phoneNumberLength is greater then seven, we add the last
221  // bit of formatting and return it.
222  return `(${phoneNumber.slice(0, 3)}) ${phoneNumber.slice(
223    3,
224    6
225  )}-${phoneNumber.slice(6, 10)}`;
226}
227
228function unstyle_phone_number(value) {
229
230  if (!value) return false;
231
232  let phoneNumber = value.replace(/[^\d]/g, ''),
233    phoneNumberLength = phoneNumber.length;
234
235  if (phoneNumberLength > 10 && phoneNumber[0] == "1") {
236    phoneNumber = phoneNumber.slice(1);
237    phoneNumberLength -= 1
238  }
239
240  if (phoneNumberLength > 10) phoneNumber = phoneNumber.slice(0,10);
241
242  if (phoneNumber.length != 10) return false;
243
244  return phoneNumber;
245}
246
247function validate_phone_number(value) {
248  return /^[0-9]{10}$/.test(value);
249}
250
251function safe_format_date_ext(date) {
252  let date_str = "";
253
254  try {
255    const main_date = date.match(/^([0-9\/]+)/)[1],
256      [day, month, year] = main_date.split("/");
257    date_str = `${day.padStart(2, "0")}/${month.padStart(2, "0")}/${year}`;
258  } catch (e) {}
259
260  return date_str;
261};
262
263function format_date_ext(date) {
264  const [day, month, year] = date.split("/");
265  return `${day.padStart(2, "0")}/${month.padStart(2, "0")}/${year}`;
266};
267
268function format_date_string(value) {
269  if (!value) return value;
270
271  const ending_slash = value[value.length - 1] == "\/";
272  let date_string = value.replace(/[^\d]/g, '');
273  const date_string_length = date_string.length;
274
275  if (date_string_length <= 2)  
276    return (ending_slash && date_string_length == 2) 
277      ? `${date_string}/`
278      : date_string;
279
280  if (date_string_length <= 4) {
281    date_string = `${date_string.slice(0, 2)}/${date_string.slice(2,4)}`;
282    return (ending_slash && date_string_length == 4) 
283      ? `${date_string}/`
284      : date_string;
285  }
286
287  return `${date_string.slice(0, 2)}/${date_string.slice(2, 4)}/${date_string.slice(4,8)}`;
288}
289
290function validate_date_string(value) {
291  try {
292    // Ensure expected format
293    if (/\d{2}\/\d{2}\/\d{4}/.test(value)) {
294      const date = moment(value);
295      return date.isValid();
296    }
297  } catch(e) {}
298
299  return false;
300}
301
302function validate_ratio_string(value) {
303  return /^[0-9]+\.?[0-9]* : [0-9]+\.?[0-9]*$/.test(value);
304}
305
306function format_ratio_string(value) {
307
308  if (!value) return "";
309
310  const match = value.match(/^\s*([0-9\.]+)[\s:]{1,}\s*([0-9\.]+)?\s*$/);
311  if (match?.length) {
312    const first_num = match[1];
313    const second_num = match[2] || "";
314    return `${first_num} : ${second_num}`;
315  }
316
317  return value;
318}
319
320const validate_state_code = (str) => STATE_CODES.includes(str);
321
322
323const NUM_UNITS = {
324  4: "million",
325  5: "ten-million",
326  6: "hundred-million",
327  7: "billion",
328  8: "ten-billion",
329  9: "hundred-billion",
330  10: "trillion",
331  11: "ten-trillion",
332  12: "hundred-trillion",
333};
334
335const style_large_number = (str) => {
336  if (typeof str == "string") str = str.replace(/[^0-9]/, "");
337  else str = String(str);
338
339  if (str.length < 7) return beautify_number(str);
340
341  var keep = str.slice(0, 3),
342    unit = str.slice(3),
343    rounder = parseInt(unit[0]),
344    num_removed = unit.length;
345
346  keep = parseInt(keep);
347
348  if (!keep || !rounder || !NUM_UNITS[unit.length]) return beautify_number(str);
349
350  if (rounder >= 5) keep += 1;
351
352  return beautify_number(keep) + " " + NUM_UNITS[unit.length];
353};
354
355const ensure_number = (x) => (typeof x == "number" ? x : parseInt(x) || 0);
356
357const parse_valid_int = (x) => {
358  if (typeof x === "number") return x;
359
360  try {
361    return parseInt(x);
362  } catch (e) {
363    return false;
364  }
365};
366
367const falsy_not_zero = (value) => !value && value !== 0;
368
369const round_distance = (ft) => {
370  var ft_in_mi = 5280,
371    mi_cutoff = ft_in_mi / 4;
372
373  if (ft >= mi_cutoff)
374    return { suffix: " mi", value: ft / ft_in_mi, include_decimal: 2 };
375  else return { suffix: " ft", value: ft };
376};
377
378const round_area = (sqft) => {
379  var sqft_in_ac = 43560,
380    sqft_in_sqmi = 27878400,
381    ac_cutoff = sqft_in_ac / 4,
382    sqmi_cuttoff = sqft_in_sqmi;
383
384  if (sqft >= sqmi_cuttoff)
385    return { suffix: " sqmi", value: sqft / sqft_in_sqmi, include_decimal: 2 };
386  else if (sqft >= ac_cutoff)
387    return { suffix: " ac", value: sqft / sqft_in_ac, include_decimal
387: 2 };
388  else return { suffix: " sqft", value: sqft };
389};
390
391const round_money = (dollar) => {
392  var dollar_in_k = 1000,
393    dollar_in_m = 1000000,
394    k_cutoff = dollar_in_k,
395    m_cuttoff = dollar_in_m;
396
397  if (dollar >= m_cuttoff)
398    return {
399      prefix: "$",
400      suffix: "M",
401      value: dollar / dollar_in_m,
402      include_decimal: 1,
403    };
404  else if (dollar >= k_cutoff)
405    return { prefix: "$", suffix: "K", value: dollar / dollar_in_k };
406  else return { prefix: "$", suffix: "", value: dollar };
407};
408
409const round_to_decimal = (num, dec) => {
410  if (typeof dec !== "number") dec = ROUND_DEC;
411  return Math.round((num + Number.EPSILON) * 10 ** dec) / 10 ** dec;
412};
413
414const is_positive_number = (value, zeros_allowed = false) => {
415  value = Number(value);
416  if (isNaN(value)) return false;
417  if (value < 0 || (!zeros_allowed && value === 0)) return false;
418  return true;
419};
420
421// Unit conversions
422
423const sqm_to_sqft = (sqm) => sqm * 10.7639;
424const ft_to_m = (ft) => ft * 0.3048;
425
426const get_current_styled_date = () => dateify_timestamp(Date.now());
427
428const dateify_utc_timestamp = (ms, time = false) => {
429  var date = new Date(ms),
430    time_text = "",
431    is_am = date.getUTCHours() < 12,
432    hour =
433      date.getUTCHours() > 12
434        ? date.getUTCHours() - 12
435        : date.getUTCHours() === 0
436        ? 12
437        : date.getUTCHours();
438
439  if (time)
440    time_text = ` at ${hour}:${number_length_x(date.getUTCMinutes(), 2)} ${
441      is_am ? "AM" : "PM"
442    }`;
443
444  return `${number_length_x(date.getUTCMonth() + 1, 2)}/${number_length_x(
445    date.getUTCDate(),
446    2
447  )}/${date.getUTCFullYear()}${time_text}`;
448};
449
450const dateify_timestamp = (ms, time = false) => {
451
452  var date = new Date(ms);
453  if (isNaN(date)) return false;
454
455  var time_text = "",
456    is_am = date.getHours() < 12,
457    hour =
458      date.getHours() > 12
459        ? date.getHours() - 12
460        : date.getHours() === 0
461        ? 12
462        : date.getHours();
463
464  if (time)
465    time_text = ` at ${hour}:${number_length_x(date.getMinutes(), 2)} ${
466      is_am ? "AM" : "PM"
467    }`;
468
469  return `${number_length_x(date.getMonth() + 1, 2)}/${number_length_x(
470    date.getDate(),
471    2
472  )}/${date.getFullYear()}${time_text}`;
473};
474
475const prettify_difference = (t1,t2,allow_future = false) => {
476  let m1 = moment(t1),
477    m2 = moment(t2),
478    diff = m1.diff(m2),
479    pretty_diff = moment.duration(m1.diff(m2)).humanize();
480
481  if (!allow_future && diff > 0) return "a few seconds ago";
482
483  return `${pretty_diff} ${diff <= 0? "ago": "from now"}`;
484}
485
486/*******************
487 * Address Helpers *
488 *******************/
489
490// Only works for:
491// (1) address_line1, address_line2, city, state zip(5)
492// (1) address_line1, city, state zip(5)
493function split_address(address_str) {
494
495  let address = {
496      address_line1: false,
497      address_line2: false,
498      city: false,
499      state: false,
500      zip: false,
501    },
502    parts = address_str.split(", ").reverse();
503
504  let [state, zip] = parts[0].split(" ");
505  zip = zip.trim();
506
507  if (/^[0-9]{5}$/.test(zip)) 
508    address.zip = zip;
509
510  if (validate_state_code(state)) 
511    address.state = state.trim();
512
513  address.city = parts[1].trim();
514  address.address_line1 = parts[parts.length - 1].trim();
515
516  if (parts.length > 3) address.address_line2 = parts[2];
517
518  return address;
519}
520
521
522/****************
523 * Date Helpers *
524 ****************/
525
526Date.prototype.addDays = function(days) {
527    var date = new Date(this.valueOf());
528    date.setDate(date.getDate() + days);
529    return date;
530}
531
532// Extend String.prototype
533String.prototype.titleCase = function () {
534  return this.replace(/\w\S*/g, function (txt) {
535    return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
536  });
537};
538
539String.prototype.snake_case = function () {
540  return this.toLowerCase().replace(/[ \t\n]+/g, "_");
541} 
542
543String.prototype.pluralize = function () {
544  return this + (/(s|sh|ch|x|z)$/.test(this) ? "(es)" : "(s)");
545};
546
547String.prototype.knownPluralize = function () {
548  return this + (/(s|sh|ch|x|z)$/.test(this) ? "es" : "s");
549};
550
551String.prototype.knownSingularize = function (lower = true) {
552  return (/^(a|e|i|o|u)/i.test(this) ? "an" : "a") + " " + (lower ? this.toLowerCase() : this);
553};
554
555String.prototype.split_first = function (sep) {
556  let [first, ...rest] = this.split(sep);
557  return [first, rest.join(sep)];
558};
559
560String.prototype.escapeQuotes = function () {
561  return this.replace(/([\'\"])/g, "\\$1");
562};
563
564String.prototype.escape_html = function () {
565  return this
566    .replace(/&/g, "&amp;")
567    .replace(/</g, "&lt;")
568    .replace(/>/g, "&gt;")
569    .replace(/"/g, "&quot;")
570    .replace(/'/g, "&#039;");
571};
572
573String.prototype.unescape_html = function () {
574  return this
575    .replace(/&amp;/g, "&")
576    .replace(/&lt;/g, "<")
577    .replace(/&gt;/g, ">")
578    .replace(/&quot;/g, "\"")
579    .replace(/&#039;/g, "'");
580};
581
582
583String.prototype.symbolize = function () {
584  return this.toLowerCase().replace(/[^a-zA-Z0-9_]+/g, "_");
585};
586
587String.prototype.chunk = function(length,token = false,includetoken = false) {
588 
589  if (!token) return this.match(new RegExp('.{1,' + length + '}', 'g'));
590
591  var string_arr = this.split(token),
592    arr = [""];
593
594  string_arr.forEach( (part,i) => {
595
596    if(
597      i !== 0
598      && (arr[arr.length - 1].length || includetoken)
599    ) part = token + part;
600
601    arr[arr.length - 1] += part;
602
603    if(arr[arr.length - 1].length >= length && i !== string_arr.length) 
604      arr.push("");
605  });
606    
607
608  return arr;
609}
610
611
612String.prototype.escape_reg_exp = function(unescaped = []) {
613  const chars = Array.difference(
614      [".","*","+","?","^","$","{","}","(",")","|","[","]","\\"],
615      unescaped
616    )
617    .join("")
618    .replace("\\","\\\\")
619    .replace("]","\\]"),
620    chars_reg_exp = new RegExp(`[${chars}]`,"g");
621
622  return this.replace(chars_reg_exp, '\\$&');
623}
624
625/* 
626  Split string into chunks as designated by a list of percentages. Any unspeced
627  percentage will be added on as one final string in the array
628  
629  the token argument allows you to specify a token that each part will be broken on (use " " 
630  as your token to round percentage-chunks up to the nearest word)
631
632  i.e.
633  s = "This is a strong this is a string this is a good string this is definetly a good string this is a good test string this"
634  s.percentchunk([40,20,20]," ",true)
635
636  ==> ['This is a strong this is a string this is a good', ' string this is definetly', ' a good string this is a', ' good test string this']
637*/
638String.prototype.percentchunk = function(p_arr,token = "",includetoken = false) {
639  
640  var percent_covered = p_arr.reduce( (sum,per) => (sum + per),0);
641
642  if (percent_covered > 100 || percent_covered <= 0) return null;
643
644  let char_arr = p_arr.map( (per) => Math.round(this.length * (per / 100)) ),
645    string_arr = this.split(token);
646
647  console.log(char_arr);
648
649  return string_arr.reduce( (chunks,part,i) => {
650
651    if(i !== 0 && includetoken) part = token + part;
652
653    if (
654      chunks[chunks.length - 1].length >= char_arr[chunks.length - 1] 
655      && i !== string_arr.length
656    ) 
657      chunks.push("");
658
659    else if (i !== 0 && !includetoken) part = token + part;
660
661    chunks[chunks.length - 1] += part;
662
663    return chunks;
664
665  }, [""]);
666}
667
668String.prototype.pretty_quotify = function() {
669  return this
670    .replace(/(^|[-\u2014\s(\["])'/g, "$1\u2018") // opening singles
671    .replace(/'/g, "\u2019") // closing singles & apostrophes
672    .replace(/(^|[-\u2014/\[(\u2018\s])"/g, "$1\u201c") // opening doubles
673    .replace(/"/g, "\u201d"); // closing doubles
674};
675
676Number.prototype.celsius_to_fahrenheit = function(round = 2) { 
677  return roundify(this * (9/5) + 32, round);
678}
679
680/* JSON utilities */
681
682const parse_valid_JSON = (str) => {
683  try {
684
685    var validated = JSON5.parse(str);
686    
687    if (typeof validated == "object")
688      return validated;
689
690  } catch (e) {}
691
692  return false;
693};
694
695/* Array utilities */
696
697// Extend Array.prototype
698Array.intersection = (arr1, arr2) => arr1.filter((x) => arr2.includes(x));
699Array.difference = (arr1, arr2) => arr1.filter((x) => !arr2.includes(x));
700Array.symetric_difference = (arr1, arr2) =>
701  arr1
702    .filter((x) => !arr2.includes(x))
703    .concat(arr2.filter((x) => !arr1.includes(x)));
704Array.union = (arr1, arr2) => [...arr1, ...arr2];
705
706Array.prototype.unique = function () {
707  var a = this.concat();
708  return a.filter((val, i) => a.indexOf(val) === i);
709};
710
711Array.prototype.average = function () {
712  return this.reduce((p, c) => p + c, 0) / this.length;
713};
714
715Array.prototype.element_replace = function (f, r) {
716  var i = this.indexOf(f);
717  if (i === -1) return;
718  this[i] = r;
719};
720
721Array.prototype.objectize = function (k) {
722  return this.reduce((obj,e) => {
723    if (typeof e == "object" && (k in e) ) {
724      let copy = {...e};
725      delete copy[k];
726      obj[ e[k] ] = copy;
727    }
728    return obj;
729  }, {}) 
730}
731
732Array.prototype.find_last = function (cond) {
733  let filtered = this.filter(cond);
734  return filtered[filtered.length - 1];
735}
736
737Array.distinct_json_array_push = (array,element,distinctattr) => {
738  if (!array.some((json) => json[distinctattr] === element[distinctattr]))
739    array.push(element);
740  return array;
741}
742
743Array.prototype.comma_separated_no_join = function (conjunction = "and", comma = ",") {
744
745  const filtered = this.filter(item => stripquotes(item));
746  
747  let needscommas = (filtered.length > 2);
748
749  return filtered.map((item,i) => {
750    item = stripquotes(item);
751
752    if (i != filtered.length - 1) {
753      if (needscommas) item = `${item}${comma}`;
754      if (i == filtered.length - 2) item = `${item} ${conjunction}`;
755    }
756
757    return item;
758  });
759}
760
761Array.prototype.comma_separated = function(conjunction = "and", comma=",") {
762  return this.comma_separated_no_join(conjunction,comma).join(" ");
763}
764
765Array.prototype.smallest = function () {
766  return [...this].sort()[0];
767}
768
769Array.prototype.pop_match = function (condition) {
770  let match = this.findIndex(condition);
771  return match !== -1 ? this.splice(match,1)[0] : undefined;
772}
773
774/* Sorts */
775function numeric_list_tier_sort(nl1, nl2) {
776
777  // Same priority
778  if (!nl1?.length && !nl2.length) return 0;
779
780  // Same priority but nl1 is additionally nested
781  if (!nl2?.length) return 1;
782
783  // Same priority but nl2 is additionally nested
784  if (!nl1?.length) return -1;
785
786  const p_nl1 = Number(nl1.slice(-1)),
787    p_nl2 = Number(nl2.slice(-1)),
788    p_nl1_isnan = isNaN(p_nl1),
789    p_nl2_isnan = isNaN(p_nl2),
790    // If both are NaN or ===, we treat as equal priority
791    same_p = (p_nl1_isnan && p_nl2_isnan) || p_nl1 === p_nl2;
792
793  // If equal, we need to move up the list to determine priority
794  if (same_p) return numeric_list_tier_sort(
795    nl1.slice(0,nl1.length - 1),
796    nl2.slice(0,nl2.length - 1)
797  );
798
799  // If nl1 is NaN, we deprioritize it
800  if (p_nl1_isnan) return 1;
801
802  // If nl2 is NaN, we deprioritize it
803  if (p_nl2_isnan) return -1;
804
805  // Use this tier as prioritization
806  return p_nl1 - p_nl2;
807}
808
809/* Promise utilities */
810
811/**
812 * Wraps an async function so that only the most recent call's result is returned.
813 * Previous pending calls will resolve with `undefined` (or remain pending depending on implementation).
814 * 
815 * @param {Function} async_fn - The async function to wrap
816 * @returns {Function} - The wrapped safe function
817 */
818function debounce_promise(async_fn) {
819    let active_request_id = 0;
820
821    return async function(...args) {
822        // Capture the ID at the moment of the call
823        const request_id = ++active_request_id;
824        
825        // Run the original function
826        const result = await async_fn.apply(this, args);
827
828        // If the ID has changed since we started, it means a newer call happened.
829        // We act as if this function never finished (return undefined).
830        if (request_id !== active_request_id) {
831            return;
832        }
833
834        return result;
835    };
836}
837
838// Extend Promise
839Promise.all_values = async (object) => {
840  return _.zipObject(_.keys(object), await Promise.all(_.values(object)))
841}
842
843
844/* Spatial conversion */
845
846const convert_to_wkt = (g) => {
847  var wkt = new Wkt.Wkt();
848  wkt.read(JSON.stringify(g));
849  return wkt.write();
850};
851
852/* Geometries */
853
854// Get info
855const getCenter = (g) => turf.coordAll(turf.center(g))[0];
856
857const point_on_feature = (feature) => {
858  var geometry = turf.lineToPolygon(turf.polygonToLine(feature)).geometry;
859  return turf.pointOnFeature(geometry);
860};
861
862const center_of_feature = (feature) => {
863  var geometry = turf.lineToPolygon(turf.polygonToLine(feature)).geometry;
864  return turf.centroid(geometry);
865};
866
867// Convert geometries
868const cornersToBBox = (c1, c2, properties = {}) =>
869  turf.convex(
870    turf.polygon([[c1, [c1[0], c2[1]], c2, [c2[0], c1[1]], c1]], properties)
871  );
872
873const latlngToBBox = (latlng) =>
874  cornersToBBox(
875    [latlng._ne.lng, latlng._ne.lat],
876    [latlng._sw.lng, latlng._sw.lat]
877  );
878
879/* Math */
880
881// Comparative selection
882const smallestLargerThan = (obj, num) => {
883  var slt = obj[Math.min(...Object.keys(obj).filter((key) => key >= num))];
884
885  return slt ? slt : obj[Math.max(...Object.keys(obj))];
886};
887
888const largestSmallerThan = (obj, num) => {
889  var lst = obj[Math.max(...Object.keys(obj).filter((key) => key <= num))];
890
891  return lst ? lst : obj[Math.min(...Object.keys(obj))];
892};
893
894// Random
895function getRandomInt(max) {
896  return Math.floor(Math.random() * max);
897}
898
899// Round
900const roundify = (x, n) => Math.round(x * 10 ** n) / 10 ** n;
901
902
903/* JQUERY */
904function append_at_index(parent,elem,index) {
905  if (index === 0)
906    parent.prepend(elem);
907  else if (parent.children().length <= index)     
908    parent.append(elem);
909  else 
910    parent.children(`*:nth-child(${index})`).after(elem);
911}
912
913
914/* Async */
915
916function promiseState(promise, callback) {
917  // Symbols and RegExps are never content-equal
918  var uniqueValue = window['Symbol'] ? Symbol('unique') : /unique/
919
920  function notifyPendingOrResolved(value) {
921    if (value === uniqueValue) {
922      return callback('pending')
923    } else {
924      return callback('fulfilled')
925    }
926  }
927
928  function notifyRejected(reason) {
929    return callback('rejected')
930  }
931  
932  var race = [promise, Promise.resolve(uniqueValue)]
933  Promise.race(race).then(notifyPendingOrResolved, notifyRejected)
934}
935
936async function wrap_async_timer(asyncfn,timeoutfn,completefn,time, options = {}) {
937
938  var start = options.start || Date.now(),
939    resolved = false,
940    timeout = false,
941    promise = asyncfn().then(() => {
942      if(!timeout) resolved = true;
943      else completefn();
944    });
945
946  setTimeout(
947    () => {
948      if(!resolved) {
949        timeout = true;
950        timeoutfn();
951      }
952    }, time
953  );
954
955}
956
957async function waitForCondition(args, checkFn, err, options = {}) {
958  var max_time = options.max_time || 250,
959    wait_time = options.wait_time || 10,
960    iterativeFn = options.iterativeFn || false,
961    result = false;
962
963  await new Promise((resolve) => {
964    var start_time = Date.now();
965    function checkFlag() {
966      try {
967        //args.length &&
968        var check = checkFn(...args);
969      } catch (e) {
970        var check = false;
971      }
972
973      if (check) {
974        result = true;
975        resolve();
976      } else if (Date.now() > start_time + max_time) {
977        // if(!options.fallback) console.log(err);
978        resolve();
979      } else {
980        if (iterativeFn) args = [iterativeFn()];
981        window.setTimeout(checkFlag, wait_time);
982      }
983    }
984    checkFlag();
985  });
986
987  return result;
988}
989
990const stop_event = (name, callback, additional = 0) => {
991  if (!STOP_EVENT[name]) STOP_EVENT[name] = 0;
992
993  STOP_EVENT[name] += 1;
994  var save_stop = STOP_EVENT[name];
995
996  setTimeout(() => {
997    if (save_stop == STOP_EVENT[name]) {
998      try {
999        callback();
1000      } catch (e) {
1001        if (additional)
1002          console.log("Error: you've selected this event too many times.");
1003        else stop_event(name, callback, STOP_EVENT_TIME);
1004      }
1005      STOP_EVENT[name] = 0;
1006    }
1007  }, STOP_EVENT_TIME + additional);
1008};
1009
1010const delay = (ms) => new Promise((res) => setTimeout(res, ms));
1011
1012async function ensure_takes_at_least(callback, min_time) {
1013  const pre_time = Date.now(),
1014    result = await callback(),
1015    time_taken = Date.now() - pre_time;
1016
1017  if (time_taken < min_time) await delay(min_time - time_taken);
1018
1019  return result;
1020};
1021
1022function is_within_days_from_date(date_string, days) {
1023  return Math.abs((new Date(date_string) - new Date()) / (1000 * 3600 * 24)) <= days;
1024}
1025
1026function current_year_last_2_digits() {
1027  const currentYear = new Date().getFullYear();
1028  return currentYear % 100;
1029}
1030
1031//dates should be in mm-dd-yyyy format in the provided timezone
1032function date_compare(date1, date2) {
1033  try {
1034    moment.suppressDeprecationWarnings = true;
1035
1036    const moment_date1 = moment(date1).unix(),
1037      moment_date2 = moment(date2).unix();
1038
1039    return moment_date1 === moment_date2
1040      ? 0
1041      : moment_date1 > moment_date2
1042      ? 1
1043      : -1;
1044  } catch {}
1045
1046  return false;
1047}
1048
1049//date should be in mm-dd-yyyy format in the provided timezone
1050function date_compare_now(date, timezone = "UTC") {
1051  const now = new Date().toLocaleDateString("en-US", {
1052    timeZone: timezone,
1053  });
1054
1055  return date_compare(date, now);
1056}
1057
1058/**************************
1059 * Parent / Child Windows *
1060 **************************/
1061
1062function get_child_window() {
1063  return $(document).find(`#${CHILD_WINDOW_ID} > iframe`)[0].contentWindow;
1064}
1065
1066function has_child_window() {
1067  const child = $(document).find(`#${CHILD_WINDOW_ID} > iframe`);
1068  return Boolean(child && child.length)
1069}
1070
1071function run_across_windows(fn_str) {
1072  if (typeof window[fn_str] !== "function") return;
1073  window[fn_str]();
1074}
1075
1076/**************
1077 * State data *
1078 **************/
1079
1080const STATES = {
1081  state_01: { abbreviation: "AL", name: "Alabama" },
1082  state_31: { abbreviation: "NE", name: "Nebraska" },
1083  state_02: { abbreviation: "AK", name: "Alaska" },
1084  state_32: { abbreviation: "NV", name: "Nevada" },
1085  state_04: { abbreviation: "AZ", name: "Arizona" },
1086  state_33: { abbreviation: "NH", name: "New Hampshire" },
1087  state_05: { abbreviation: "AR", name: "Arkansas" },
1088  state_34: { abbreviation: "NJ", name: "New Jersey" },
1089  state_06: { abbreviation: "CA", name: "California" },
1090  state_35: { abbreviation: "NM", name: "New Mexico" },
1091  state_08: { abbreviation: "CO", name: "Colorado" },
1092  state_36: { abbreviation: "NY", name: "New York" },
1093  state_09: { abbreviation: "CT", name: "Connecticut" },
1094  state_37: { abbreviation: "NC", name: "North Carolina" },
1095  state_10: { abbreviation: "DE", name: "Delaware" },
1096  state_38: { abbreviation: "ND", name: "North Dakota" },
1097  state_11: { abbreviation: "DC", name: "District of Columbia" },
1098  state_39: { abbreviation: "OH", name: "Ohio" },
1099  state_12: { abbreviation: "FL", name: "Florida" },
1100  state_40: { abbreviation: "OK", name: "Oklahoma" },
1101  state_13: { abbreviation: "GA", name: "Georgia" },
1102  state_41: { abbreviation: "OR", name: "Oregon" },
1103  state_15: { abbreviation: "HI", name: "Hawaii" },
1104  state_42: { abbreviation: "PA", name: "Pennsylvania" },
1105  state_16: { abbreviation: "ID", name: "Idaho" },
1106  state_72: { abbreviation: "PR", name: "Puerto Rico" },
1107  state_17: { abbreviation: "IL", name: "Illinois" },
1108  state_44: { abbreviation: "RI", name: "Rhode Island" },
1109  state_18: { abbreviation: "IN", name: "Indiana" },
1110  state_45: { abbreviation: "SC", name: "South Carolina" },
1111  state_19: { abbreviation: "IA", name: "Iowa" },
1112  state_46: { abbreviation: "SD", name: "South Dakota" },
1113  state_20: { abbreviation: "KS", name: "Kansas" },
1114  state_47: { abbreviation: "TN", name: "Tennessee" },
1115  state_21: { abbreviation: "KY", name: "Kentucky" },
1116  state_48: { abbreviation: "TX", name: "Texas" },
1117  state_22: { abbreviation: "LA", name: "Louisiana" },
1118  state_49: { abbreviation: "UT", name: "Utah" },
1119  state_23: { abbreviation: "ME", name: "Maine" },
1120  state_50: { abbreviation: "VT", name: "Vermont" },
1121  state_24: { abbreviation: "MD", name: "Maryland" },
1122  state_51: { abbreviation: "VA", name: "Virginia" },
1123  state_25: { abbreviation: "MA", name: "Massachusetts" },
1124  state_78: { abbreviation: "VI", name: "Virgin Islands" },
1125  state_26: { abbreviation: "MI", name: "Michigan" },
1126  state_53: { abbreviation: "WA", name: "Washington" },
1127  state_27: { abbreviation: "MN", name: "Minnesota" },
1128  state_54: { abbreviation: "WV", name: "West Virginia" },
1129  state_28: { abbreviation: "MS", name: "Mississippi" },
1130  state_55: { abbreviation: "WI", name: "Wisconsin" },
1131  state_29: { abbreviation: "MO", name: "Missouri" },
1132  state_56: { abbreviation: "WY", name: "Wyoming" },
1133  state_30: { abbreviation: "MT", name: "Montana" },
1134  state_60: { abbreviation: "AS", name: "American Samoa" },
1135  state_64: { abbreviation: "FM", name: "Federated States of Micronesia" },
1136  state_66: { abbreviation: "GU", name: "Guam" },
1137  state_68: { abbreviation: "MH", name: "Marshall Islands" },
1138  state_69: { abbreviation: "MP", name: "Northern Mariana Islands" },
1139  state_70: { abbreviation: "PW", name: "Palau" },
1140};
1141
1142const STATE_CODES = Object.values(STATES).map((state_info) => state_info.abbreviation).sort();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.