PageSourceSearch

https://portal.jumbotech.co.za/assets/js/custom.js?v=2dbb33169c70d69cf9ae3258ec2aad68

js jumbotech.co.za collected 2026-10-08 17:31:46 UTC 54,137 bytes, 1,505 lines download raw bytes

1// Pass the id of the initial select2 tag that is used
2// to create the select2
3function select2_has_error(el, color='#A94442'){
4    var tag = $(el).select();
5    if ( tag ) {
6        var link_bound = tag[0].previousElementSibling.childNodes[0];
7        $(link_bound).css('border-color', color);
8    }
9}
10
11function select2_no_error(el){
12    select2_has_error(el, '');
13}
14
15// Toggle password visibility input by id
16//
17function toggle_on(password){
18    password.type = "text";
19}
20function toggle_off(password){
21    //Add timer hide if copy is needed
22    setTimeout(function(){
23        password.type = "password";
24    }, 2000);
25}
26
27Number.prototype.length_between = function(a, b, inclusive) {
28    var min = Math.min(a, b),
29    max = Math.max(a, b),
30    len = this.toString().length;
31    return inclusive ? len >= min && len <= max : len > min && len < max;
32}
33
34function urlEncode(s){
35    return encodeURIComponent( s ).replace( /\%20/g, '+' ).replace( /!/g, '%21' ).replace( /'/g, '%27' ).replace( /\(/g, '%28' ).replace( /\)/g, '%29' ).replace( /\*/g, '%2A' ).replace( /\~/g, '%7E' );
36}
37
38function urlDecode(s){
39    return decodeURIComponent( s.replace( /\+/g, '%20' ).replace( /\%21/g, '!' ).replace( /\%27/g, "'" ).replace( /\%28/g, '(' ).replace( /\%29/g, ')' ).replace( /\%2A/g, '*' ).replace( /\%7E/g, '~' ) );
40}
41
42function urlEncode2 (str) {
43    return encodeURIComponent(str).
44        // Note that although RFC3986 reserves "!", RFC5987 does not,
45        // so we do not need to escape it
46        replace(/['()]/g, escape). // i.e., %27 %28 %29
47        replace(/\*/g, '%2A').
48            // The following are not required for percent-encoding per RFC5987,
49            // so we can allow for a little better readability over the wire: |`^
50            replace(/%(?:7C|60|5E)/g, unescape);
51}
52
53// adds '!' to encodeURIComponent
54~function () {
55    var orig = window.encodeURIComponent;
56    window.encodeURIComponent = function (str) {
57        // calls the original function, and adds your
58        // functionality to it
59        return orig.call(window, str).replace(/!/g, '%21').replace(/\*/g,'%2A').replace(/\$/g,'%24');
60    };
61}();
62
63function escapeHtml(text) {
64  return text
65      .replace(/&/g, "&amp;")
66      .replace(/</g, "&lt;")
67      .replace(/>/g, "&gt;")
68      .replace(/"/g, "&quot;")
69      .replace(/'/g, "&#039;");
70}
71
72function IsAlphaNumeric(input) {
73    var regex = /[^a-zA-Z0-9]/;
74    return regex.test(input);
75}
76
77function IsEmail(emails)
78{
79    var res = emails.split(",");
80    for(i = 0; i < res.length; i++)
81        if(!validateEmail(res[i])) return false;
82    return true;
83}
84
85function dt_sleep(milliseconds) {
86    var start = new Date().getTime();
87    for (var i = 0; i < 1e7; i++) {
88        if ((new Date().getTime() - start) > milliseconds){
89            break;
90        }
91    }
92}
93
94function validateEmail(email){
95  var regex = /^([a-zA-Z0-9_\.\-\+\&])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,10})+$/;
96  return regex.test(email);
97}
98function IsPhoneNumber(num) {
99    var regex = /^[0-9-+]+$/;
100    if (num.length < 10){ return false; }
101    return regex.test(num);
102}
103function isMacAddress(mac){
104    var regex = /^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$/;
105    return regex.test(mac);
106}
107function isNumber(num){
108    num = num.replace(",","");
109    return !isNaN(num);
110}
111function IsPassword(input){
112    var regex = /^.*[a-zA-Z0-9\@\^\~\+=\\\*\(\)\{<\}\$\!%\#\?&\|>\[\}\-\/\.\,\;\:_]{6,}$/;
113    return regex.test(input);
114}
115function IsTime(input,format){
116    var regex = /^([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/;
117	if (format=="hh:mm"){
118		regex = /^([0-1][0-9]|2[0-3]):([0-5][0-9])$/;
119	}
120    return regex.test(input);
121}
122function isDate(input,format){
123    var regex= "";
124    if (format===undefined || format=="yyyy-mm-dd"){
125		regex = /^\d{4}[\-\/\s]?((((0[13578])|(1[02]))[\-\/\s]?(([0-2][0-9])|(3[01])))|(((0[469])|(11))[\-\/\s]?(([0-2][0-9])|(30)))|(02[\-\/\s]?[0-2][0-9]))$/;
126	}
127	return regex.test(input);
128}
129function IsValidIP(input){
130	if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(input)){ 
131		return true;
132	} else {
133		return false;
134	}
135}
136function IsIDnumber(input){
137    var regex = /(((\d{2}((0[13578]|1[02])(0[1-9]|[12]\d|3[01])|(0[13456789]|1[012])(0[1-9]|[12]\d|30)|02(0[1-9]|1\d|2[0-8])))|([02468][048]|[13579][26])0229))(( |-)(\d{4})( |-)(\d{3})|(\d{7}))$/;
138    return regex.test(input);
139}
140
141function isValidGPS(input){
142	var regex = /([+-]?\d+\.?\d+)\s*,\s*([+-]?\d+\.?\d+)/;
143	return regex.test(input.trim());
144}
145
146function isValidURL(input) {
147	var regex = /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/;
148	return regex.test(input.trim());
149}
150
151function isValidDomain(input) {
152	var regex = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/;
153	return regex.test(input.trim());
154}
155
156/*
157function ValidateIPAddress($checkipaddress) {
158	var IpAddReg = /^([1-9][0-9]{0,1}|1[0-9]{2}|2[0-4][0-9]|25[0-4])\.([1-9][0-9]{0,1}|1[0-9]{2}|2[0-4][0-9]|25[0-4])\.([1-9][0-9]{0,1}|1[0-9]{2}|2[0-4][0-9]|25[0-4])\.([1-9][0-9]{0,1}|1[0-9]{2}|2[0-4][0-9]|25[0-4])$/;
159	return IpAddReg.test( $checkipaddress );
160};
161*/
162
163function Ucfirst(string)
164{
165    return string.charAt(0).toUpperCase() + string.slice(1);
166}
167
168var ordinalize = function(n) {
169   var s=["th","st","nd","rd"],
170       v=n%100;
171   return n+(s[(v-20)%10]||s[v]||s[0]);
172}
173
174function today(){
175    var d = new Date();
176    var month = d.getMonth()+1;
177    var day = d.getDate();
178    var output = d.getFullYear() + '-' +
179        ((''+month).length<2 ? '0' : '') + month + '-' +
180        ((''+day).length<2 ? '0' : '') + day;
181    return(output);
182}
183
184function formatNum(nStr)
185{
186	return accounting.formatMoney(nStr, '', 2, ",", ".");
187/*
188	nStr += '';
189	x = nStr.split('.');
190	x1 = x[0];
191	x2 = x.length > 1 ? '.' + x[1] : '';
192	var rgx = /(\d+)(\d{3})/;
193	while (rgx.test(x1)) {
194	x1 = x1.replace(rgx, '$1' + ',' + '$2');
195	}
196	return x1 + x2;
197*/
198}
199
200function removeNL(s) {
201  r = "";
202  for (i=0; i < s.length; i++) {
203    if (s.charAt(i) != '\n' &&
204        s.charAt(i) != '\r' &&
205        s.charAt(i) != '\t') {
206      r += s.charAt(i);
207      } else {
208      r += ' ';
209      }
210    }
211  return r;
212}
213
214function amount(amt, label) {
215    return amt.toFixed(2);
216}
217
218function formatMoney(n, c, d, t){
219    var c = isNaN(c = Math.abs(c)) ? 2 : c,
220        d = d == undefined ? "." : d,
221        t = t == undefined ? "," : t,
222        s = n < 0 ? "-" : "",
223        i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "",
224        j = (j = i.length) > 3 ? j % 3 : 0;
225   return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
226};
227
228function bytes(bytes, label) {
229    if (bytes == 0) return ;
230    var s = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
231    var e = Math.floor(Math.log(bytes)/Math.log(1024));
232    var value = ((bytes/Math.pow(1024, Math.floor(e))).toFixed(1));
233    e = (e<0) ? (-e) : e;
234    if (label) value += ' ' + s[e];
235    return value;
236}
237function speed(bytes, label) {
238    if (bytes == 0) return ;
239    var s = ['b/s', 'Kb/s', 'Mb/s', 'Gb/s', 'Tb/s', 'Pb/s'];
240    var e = Math.floor(Math.log(bytes)/Math.log(1024));
241    var value = ((bytes/Math.pow(1024, Math.floor(e))).toFixed(1));
242    e = (e<0) ? (-e) : e;
243    if (label) value += ' ' + s[e];
244    return value;
245}
246function data_speed(bytes, label) {
247    if (bytes == 0) return ;
248    var s = ['b/s', 'Kb/s', 'Mb/s', 'Gb/s', 'Tb/s', 'Pb/s'];
249    var e = Math.floor(Math.log(bytes)/Math.log(1024));
250    var value = ((bytes/Math.pow(1024, Math.floor(e))).toFixed(1));
251    e = (e<0) ? (-e) : e;
252    if (label) value += ' ' + s[e];
253    return value;
254}
255
256function speed_1000(bytes, label) {
257    if (bytes == 0) return 0;
258    if (bytes < 0){bytes = bytes * -1;}
259    var s = ['b/s', 'Kb/s', 'Mb/s', 'Gb/s', 'Tb/s', 'Pb/s'];
260    var e = Math.floor(Math.log(bytes)/Math.log(1000));
261    var value = ((bytes/Math.pow(1000, Math.floor(e))).toFixed(1));
262    e = (e<0) ? (-e) : e;
263    if (label) value += ' ' + s[e];
264    return value;
265}
266
267function sec_to_time(secs){
268//  var ts = (new Date).clearTime().addSeconds(secs).toString('H:mm:ss');
269//  return ts;
270
271    var sec_num = parseInt(secs, 10); // don't forget the second param
272    var hours   = Math.floor(sec_num / 3600);
273    var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
274    var seconds = sec_num - (hours * 3600) - (minutes * 60);
275
276    if (hours   < 10) {hours   = "0"+hours;}
277    if (minutes < 10) {minutes = "0"+minutes;}
278    if (seconds < 10) {seconds = "0"+seconds;}
279    var time    = hours+':'+minutes+':'+seconds;
280    return time;
281}
282
283function sec_to_time_days(secs){
284//  var ts = (new Date).clearTime().addSeconds(secs).toString('H:mm:ss');
285//  return ts;
286
287    var sec_num = parseInt(secs, 10); // don't forget the second param
288    var days    = Math.floor(sec_num / 86400);
289    var hours   = Math.floor(sec_num / 3600);
290    var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
291    var seconds = sec_num - (hours * 3600) - (minutes * 60);
292
293    if (hours   > 23) {hours   = hours - (Math.floor(hours / 24)*24);}
294    if (hours   < 10) {hours   = "0"+hours;}
295    if (minutes < 10) {minutes = "0"+minutes;}
296    if (seconds < 10) {seconds = "0"+seconds;}
297    var time    = days+' days '+hours+':'+minutes+':'+seconds;
298    return time;
299}
300
301function nl2br (str, is_xhtml) {
302    var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';
303    return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2');
304}
305
306String.prototype.toUpperCaseFirstChar = function() {
307    return this.substr( 0, 1 ).toUpperCase() + this.substr( 1 );
308}
309
310function guid() {
311  function s4() {
312    return Math.floor((1 + Math.random()) * 0x10000)
313      .toString(16)
314      .substring(1);
315  }
316  return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
317}
318
319/*
320$.noty.defaults = {
321    layout: 'top',
322    theme: 'bootstrapTheme', // or 'relax'
323    type: 'alert',
324    text: '', // can be html or string
325    dismissQueue: true, // If you want to use queue feature set this true
326    template: '<div class="noty_message"><span class="noty_text"></span><div class="noty_close"></div></div>',
327    animation: {
328        open: {height: 'toggle'}, // or Animate.css class names like: 'animated bounceInLeft'
329        close: {height: 'toggle'}, // or Animate.css class names like: 'animated bounceOutLeft'
330        easing: 'swing',
331        speed: 500 // opening & closing animation speed
332    },
333    timeout: false, // delay for closing event. Set false for sticky notifications
334    force: false, // adds notification to the beginning of queue when set to true
335    modal: false,
336    maxVisible: 5, // you can set max visible notification for dismissQueue true option,
337    killer: false, // for close all notifications before show
338    closeWith: ['click'], // ['click', 'button', 'hover', 'backdrop'] // backdrop click will close all notifications
339    callback: {
340        onShow: function() {},
341        afterShow: function() {},
342        onClose: function() {},
343        afterClose: function() {},
344        onCloseClick: function() {},
345    },
346    buttons: false // an array of buttons
347};
348*/
349
350
351function show_notify(message_text,message_type,message_position,message_duration,message_container){
352    if (message_position === undefined  ){ message_position = 'topRight';}
353    if (message_type === undefined      ){ message_type = 'alert';}
354    if (message_duration === undefined  ){ message_duration = 7500;}
355    if (message_container === undefined ){ message_container = null;}
356    
357
358	new Noty({
359	    text: message_text,
360	    type: message_type,
361	    theme: 'bootstrap-v3',
362	    timeout: message_duration,
363	    layout: message_position,
364	    progressBar: true,
365	    container: message_container
366	}).show();
367
368/*
369    new Noty({
370        text: message_text,
371        layout: message_position,
372        theme: 'bootstrapTheme',
373        type: message_type,
374        dismissQueue: true, // If you want to use queue feature set this true
375        template: '<div class="noty_message"><span class="noty_text"></span><div class="noty_close"></div></div>',
376        animation: {
377            open: {height: 'toggle'},
378            close: {height: 'toggle'},
379            easing: 'swing',
380            speed: 500 // opening & closing animation speed
381        },
382        timeout: message_duration, // delay for closing event. Set false for sticky notifications
383        force: false, // adds notification to the beginning of queue when set to true
384        modal: false,
385        maxVisible: 5, // you can set max visible notification for dismissQueue true option
386        closeWith: ['click'], // ['click', 'button', 'hover']
387        callback: {
388            onShow: function() {},
389            afterShow: function() {},
390            onClose: function() {},
391            afterClose: function() {}
392        },
393        buttons: false // an array of buttons
394    }).show();
395*/
396}
397
398function confirm_notify(message_text,message_type,message_position,message_duration){
399    if (message_position === undefined  ){ message_position = 'topRight';}
400    if (message_type === undefined      ){ message_type = 'alert';}
401    if (message_duration === undefined  ){ message_duration = 10000;}
402
403	new Noty({
404	    text: message_text,
405	    type: message_type,
406	    timeout: false,
407	    closeWith: ['click'],
408	}).show();
409
410/*
411    new Noty({
412        text: message_text,
413        layout: message_position,
414        theme: 'bootstrapTheme',
415        type: message_type,
416        dismissQueue: true, // If you want to use queue feature set this true
417        template: '<div class="noty_message"><span class="noty_text"></span><div class="noty_close"></div></div>',
418        animation: {
419            open: {height: 'toggle'},
420            close: {height: 'toggle'},
421            easing: 'swing',
422            speed: 500 // opening & closing animation speed
423        },
424        timeout: false, // delay for closing event. Set false for sticky notifications
425        force: false, // adds notification to the beginning of queue when set to true
426        modal: true,
427        maxVisible: 5, // you can set max visible notification for dismissQueue true option
428        closeWith: ['click'], // ['click', 'button', 'hover']
429        callback: {
430            onShow: function() {},
431            afterShow: function() {},
432            onClose: function() {},
433            afterClose: function() {}
434        },
435          buttons: [
436            {addClass: 'btn btn-default btn-sm', text: 'Ok', onClick: function($noty) {
437                $noty.close();
438              }
439            }
440          ]
441    }).show();
442*/
443}
444
445var graph_fonts = "Helvetica,Arial,Sans-Serif ";
446var graph_font_size = "normal 11px ";
447var graph_title_size = "normal 12px ";
448var graph_legend_size = "12px ";
449
450var red_to_green = [
451    '#00ff00',
452    '#33ff00',
453    '#66ff00',
454    '#99ff00',
455    '#ccff00',
456    '#ffff00',
457    '#ffcc00',
458    '#ff9900',
459    '#ff6600',
460    '#ff3300',
461    '#ff0000',
462]
463var graph_colors =  [
464    '#ff0000',
465    '#2f7ed8',
466    '#8bbc21',
467    '#910000',
468    '#1aadce',
469    '#492970',
470    '#f28f43',
471    '#77a1e5',
472    '#c42525',
473    '#89A54E',
474    '#4572A7',
475    '#80699B',
476    '#AA4643',
477    '#ccc',
478    '#888',
479    '#a6c96a',
480    '#3D96AE',
481    '#DB843D',
482    '#92A8CD',
483    '#A47D7C',
484    '#B5CA92',
485    '#a6cee3',
486    '#1f78b4',
487    '#8bbc21',
488    '#b2df8a',
489    '#33a02c',
490    '#fb9a99',
491    '#e31a1c',
492    '#fdbf6f',
493    '#ff7f00',
494    '#cab2d6',
495    '#6a3d9a',
496    '#ffff99',
497    '#b15928'
498];
499
500var graph_colors_workflow =  [
501    '#2f7ed8',
502    '#8bbc21',
503    '#1aadce',
504    '#492970',
505    '#f28f43',
506    '#77a1e5',
507    '#c42525',
508    '#89A54E',
509    '#4572A7',
510    '#80699B',
511    '#AA4643',
512    '#ccc',
513    '#888',
514    '#a6c96a',
515    '#3D96AE',
516    '#DB843D',
517    '#92A8CD',
518    '#A47D7C',
519    '#B5CA92',
520    '#a6cee3',
521    '#1f78b4',
522    '#8bbc21',
523    '#b2df8a',
524    '#33a02c',
525    '#fb9a99',
526    '#e31a1c',
527    '#fdbf6f',
528    '#ff7f00',
529    '#cab2d6',
530    '#6a3d9a',
531    '#ffff99',
532    '#b15928',
533    '#ff0000',
534    '#910000'
535];
536
537var phone_colors = [
538    '#80699B',
539    '#89A54E'
540];
541
542var data_colors = [
543    '#6BB848',
544    '#75CC58'
545];
546
547var traffic_colors = [
548//   '#87CEFA',
549   '#4682B4',
550   '#9ABD93',
551//   '#CD748F'
552];
553
554
555(function($) {
556    $.fn.spin = function(opts, color) {
557        var presets = {
558            "tiny": { lines: 8, length: 2, width: 2, radius: 3 },
559            "small": { lines: 8, length: 4, width: 3, radius: 5 },
560            "large": { lines: 10, length: 8, width: 4, radius: 8 }
561        };
562        if (Spinner) {
563            return this.each(function() {
564                var $this = $(this),
565                    data = $this.data();
566
567                if (data.spinner) {
568                    data.spinner.stop();
569                    delete data.spinner;
570                }
571                if (opts !== false) {
572                    if (typeof opts === "string") {
573                        if (opts in presets) {
574                            opts = presets[opts];
575                        } else {
576                            opts = {};
577                        }
578                        if (color) {
579                            opts.color = color;
580                        }
581                    }
582                    data.spinner = new Spinner($.extend({color: $this.css('color')}, opts)).spin(this);
583                }
584            });
585        } else {
586            throw "Spinner class not available.";
587        }
588    };
589    $.customerSelect = function(selector,placeholder,list,prepaid) {
590        var placeholder = (typeof placeholder	!== 'undefined' && placeholder.length > 0 ? placeholder : 'Select a customer');
591        var prepaid		= (typeof prepaid		!== 'undefined' && prepaid > 0 ? 1 : 0);
592        return $(selector).select2({
593            'minimumResultsForSearch': 5,
594            'placeholder' : placeholder,
595            'allowClear' : true,
596            'ajax' : {
597                'url' : datatill.url('customers/ajax_search'),
598                'dataType' : 'JSON',
599                'quietMillis' : 250,
600                'data' : function(term,page) {
601                    return {
602                        'query'		: term,
603                        'page'		: page,
604                        'prepaid'	: (prepaid >0 ? 1 : 0),
605
606                    };
607                },
608                'results' : function(data, page) {
609                    var more = (page * 30) < data.total_rows;
610                    return {
611                        results : data.items,
612                        'text': function(item) {
613                            return item.title;
614                        },
615                        'more': more,
616                    };
617                },
618                'cache' : true
619            },
620            'initSelection' : function(el,callback) {
621                var id = $(el).val();
622
623                    $.ajax({
624                        'dataType' : 'JSON',
625                        'data' : {
626                            id : id
627                        },
628                        'url' : datatill.url('customers/ajax_get'),
629                        'quietMillis' : 250,
630                        'success' : function(data,xhr) {
631                            callback({
632                                id : data.id,
633                                text : data.accounting_code+'-'+data.customer_title
634                            });
635                        },
636                        'error' : function(xhr) {
637
638                        }
639
640                    });
641            }
642        });
643    };
644    $.customerMultiSelect = function(selector,placeholder) {
645        var placeholder = (typeof placeholder !== 'undefined' && placeholder.length > 0 ? placeholder : 'Select customers');
646        return $(selector).select2({
647            'minimumResultsForSearch': 5,
648			'multiple' : true,
649            'placeholder' : placeholder,
650            'allowClear' : true,
651            'ajax' : {
652                'url' : datatill.url('customers/ajax_search'),
653                'dataType' : 'JSON',
654                'quietMillis' : 250,
655                'data' : function(term,page) {
656                    return {
657                        'query' : term,
658                        'page' : page
659                    };
660                },
661                'results' : function(data, page) {
662                    var more = (page * 30) < data.total_rows;
663                    return {
664                        results : data.items,
665                        'text': function(item) {
666                            return item.title;
667                        },
668                        'more': more,
669                    };
670                },
671                'cache' : true
672            },
673            'initSelection' : function(el,callback) {
674                var id = $(el).val();
675
676                    $.ajax({
677                        'dataType' : 'JSON',
678                        'data' : {
679                            id : id
680                        },
681                        'url' : datatill.url('customers/ajax_get'),
682                        'quietMillis' : 250,
683                        'success' : function(data,xhr) {
684                            callback({
685                                id : data.id,
686                                text : data.accounting_code+'-'+data.customer_title
687                            });
688                        },
689                        'error' : function(xhr) {
690
691                        }
692
693                    });
694            }
695        });
696    };
697    $.customerDebtorsSelect = function(selector,placeholder,list) {
698        var placeholder = (typeof placeholder	!== 'undefined' && placeholder.length > 0 ? placeholder : 'Select a customer');
699        
700        return $(selector).select2({
701            'minimumResultsForSearch': 5,
702            'placeholder' : placeholder,
703            'allowClear' : true,
704            'ajax' : {
705                'url' : datatill.url('customers/ajax_debtors_search'),
706                'dataType' : 'JSON',
707                'quietMillis' : 250,
708                'data' : function(term,page) {
709                    return {
710                        'query'		: term,
711                        'page'		: page,
712
713                    };
714                },
715                'results' : function(data, page) {
716                    var more = (page * 30) < data.total_rows;
717                    return {
718                        results : data.items,
719                        'text': function(item) {
720                            return item.title;
721                        },
722                        'more': more,
723                    };
724                },
725                'cache' : true
726            },
727            'initSelection' : function(el,callback) {
728                var id = $(el).val();
729
730                    $.ajax({
731                        'dataType' : 'JSON',
732                        'data' : {
733                            id : id
734                        },
735                        'url' : datatill.url('customers/ajax_get'),
736                        'quietMillis' : 250,
737                        'success' : function(data,xhr) {
738                            callback({
739                                id : data.id,
740                                text : data.accounting_code+'-'+data.customer_title
741                            });
742                        },
743                        'error' : function(xhr) {
744
745                        }
746
747                    });
748            }
749        });
750    };
751    $.resellerCustomerSelect = function(selector,placeholder) {
752        var placeholder = (typeof placeholder !== 'undefined' && placeholder.length > 0 ? placeholder : 'Select a customer');
753        return $(selector).select2({
754            'minimumResultsForSearch': 5,
755            'placeholder' : placeholder,
756            'allowClear' : true,
757            'ajax' : {
758                'url' : datatill.url('customers/ajax_reseller_customer_search'),
759                'dataType' : 'JSON',
760                'quietMillis' : 250,
761                'data' : function(term,page) {
762                    return {
763                        'query' : term,
764                        'page' : page
765                    };
766                },
767                'results' : function(data, page) {
768                    var more = (page * 30) < data.total_rows;
769                    return {
770                        results : data.items,
771                        'text': function(item) {
772                            return item.title;
773                        },
774                        'more': more,
775                    };
776                },
777                'cache' : true
778            },
779            'initSelection' : function(el,callback) {
780                var id = $(el).val();
781                    $.ajax({
782                        'dataType' : 'JSON',
783                        'data' : {
784                            id : id
785                        },
786                        'url' : datatill.url('customers/ajax_resellers_get'),
787                        'quietMillis' : 250,
788                        'success' : function(data,xhr) {
789                            callback({
790                                id : data.id,
791                                text : data.accounting_code+'-'+data.customer_title
792                            });
793                        },
794                        'error' : function(xhr) {
795
796                        }
797
798                    });
799            }
800        });
801    };
802    $.resellerCustomerMultiSelect = function(selector,placeholder) {
803        var placeholder = (typeof placeholder !== 'undefined' && placeholder.length > 0 ? placeholder : 'Select customers');
804        return $(selector).select2({
805            'minimumResultsForSearch': 5,
806            'placeholder' : placeholder,
807			'multiple' : true,
808            'allowClear' : true,
809            'ajax' : {
810                'url' : datatill.url('customers/ajax_reseller_customer_search'),
811                'dataType' : 'JSON',
812                'quietMillis' : 250,
813                'data' : function(term,page) {
814                    return {
815                        'query' : term,
816                        'page' : page
817                    };
818                },
819                'results' : function(data, page) {
820                    var more = (page * 30) < data.total_rows;
821                    return {
822                        results : data.items,
823                        'text': function(item) {
824                            return item.title;
825                        },
826                        'more': more,
827                    };
828                },
829                'cache' : true
830            },
831            'initSelection' : function(el,callback) {
832                var id = $(el).val();
833                    $.ajax({
834                        'dataType' : 'JSON',
835                        'data' : {
836                            id : id
837                        },
838                        'url' : datatill.url('customers/ajax_resellers_get'),
839                        'quietMillis' : 250,
840                        'success' : function(data,xhr) {
841                            callback({
842                                id : data.id,
843                                text : data.accounting_code+'-'+data.customer_title
844                            });
845                        },
846                        'error' : function(xhr) {
847
848                        }
849
850                    });
851            }
852        });
853    };
854    $.subaccountsCustomerSelect = function(selector,placeholder) {
855        var placeholder = (typeof placeholder !== 'undefined' && placeholder.length > 0 ? placeholder : 'Select a customer');
856        return $(selector).select2({
857            'minimumResultsForSearch': 5,
858            'placeholder' : placeholder,
859            'allowClear' : true,
860            'ajax' : {
861                'url' : datatill.url('customers/ajax_subaccounts_customer_search'),
862                'dataType' : 'JSON',
863                'quietMillis' : 250,
864                'data' : function(term,page) {
865                    return {
866                        'id' : $('#id').val(),
867                        'query' : term,
868                        'page' : page
869                    };
870                },
871                'results' : function(data, page) {
872                    var more = (page * 30) < data.total_rows;
873                    return {
874                        results : data.items,
875                        'text': function(item) {
876                            return item.title;
877                        },
878                        'more': more,
879                    };
880                },
881                'cache' : true
882            },
883            'initSelection' : function(el,callback) {
884                var id = $(el).val();
885                    $.ajax({
886                        'dataType' : 'JSON',
887                        'data' : {
888                            id : id
889                        },
890                        'url' : datatill.url('customers/ajax_subaccounts_get'),
891                        'quietMillis' : 250,
892                        'success' : function(data,xhr) {
893                            callback({
894                                id : data.id,
895                                text : data.accounting_code+'-'+data.customer_title
896                            });
897                        },
898                        'error' : function(xhr) {
899
900                        }
901
902                    });
903            }
904        });
905    };
906    $.radiusSelect = function(selector,account_type,result_type,customer_id,parent_only,include_prepaid,include_expired,include_lte,include_mikrotik,placeholder,multiple) {
907        var placeholder			= (typeof placeholder 		!== 'undefined' && placeholder.length		> 0 ? placeholder : 'Select a data account');
908        var result_type			= (typeof result_type		!== 'undefined' && result_type.length		> 0 ? result_type : 'radius');
909        var multiple			= (typeof multiple			!== 'undefined' && multiple					> 0 ? true : false);
910
911//        var parent_only		= (typeof parent_only !== 'undefined' && parent_only.length > 0 ? parent_only : 0);
912 //       var include_prepaid	= (typeof include_prepaid !== 'undefined' && include_prepaid.length > 0 ? include_prepaid : 0);
913        return $(selector).select2({
914            'minimumResultsForSearch': 5,
915            'placeholder' : placeholder,
916            'allowClear' : true,
917			'multiple': multiple,
918            'ajax' : {
919                'url' : datatill.url('radius/ajax_search'),
920                'dataType' : 'JSON',
921                'quietMillis' : 250,
922                'data' : function(term,page) {
923                    return {
924                        'query'				: term,
925                        'page'				: page,
926                        'account_type'		: account_type,
927                        'result_type'		: result_type,
928                        'parent_only'		: parent_only,
929                        'include_prepaid'	: include_prepaid,
930                        'include_expired'	: include_expired,
931                        'customer_id'		: customer_id,
932                        'include_lte'		: (include_lte>0 ? 1 : 0),
933                        'include_mikrotik'	: (include_mikrotik>0 ? 1 : 0)
934                    };
935                },
936                'results' : function(data, page) {
937                    var more = (page * 30) < data.total_rows;
938                    return {
939                        results : data.items,
940                        'text': function(item) {
941                            return item.title;
942                        },
943                        'more': more,
944                    };
945                },
946                'cache' : true
947            },
948            'initSelection' : function(el,callback) {
949                var id = $(el).val();
950
951                    $.ajax({
952                        'dataType' : 'JSON',
953                        'data' : {
954                            id : id
955                        },
956                        'url' : datatill.url('radius/ajax_get'),
957                        'quietMillis' : 250,
958                        'success' : function(data,xhr) {
959	                        if (result_type == 'customer'){
960	                            callback({
961	                                id : data.id,
962	                                text : (data.alias>'' ? data.alias+ ' ('+data.username+')' : data.username)+' - '+data.customer_title
963	                            });
964	                        } else {
965	                            callback({
966	                                id : data.id,
967	                                text : (data.alias>'' ? data.alias+ ' ('+data.username+')' : data.username)
968	                            });
969	                        }
970                        },
971                        'error' : function(xhr) {
972
973                        }
974
975                    });
976            }
977        });
978    };
979      $.customerPortaSelect = function(selector,placeholder,list,prepaid) {
980        var placeholder = (typeof placeholder	!== 'undefined' && placeholder.length > 0 ? placeholder : 'Select a customer');
981        var prepaid		= (typeof prepaid		!== 'undefined' && prepaid > 0 ? 1 : 0);
982        return $(selector).select2({
983            'minimumResultsForSearch': 5,
984            'placeholder' : placeholder,
985            'allowClear' : true,
986            'ajax' : {
987                'url' : datatill.url('customers/ajax_porta_search'),
988                'dataType' : 'JSON',
989                'quietMillis' : 250,
990                'data' : function(term,page) {
991                    return {
992                        'query'		: term,
993                        'page'		: page,
994                        'prepaid'	: (prepaid >0 ? 1 : 0),
995
996                    };
997                },
998                'results' : function(data, page) {
999                    var more = (page * 30) < data.total_rows;
1000                    return {
1001                        results : data.items,
1002                        'text': function(item) {
1003                            return item.title;
1004                        },
1005                        'more': more,
1006                    };
1007                },
1008                'cache' : true
1009            },
1010            'initSelection' : function(el,callback) {
1011                var id = $(el).val();
1012
1013                    $.ajax({
1014                        'dataType' : 'JSON',
1015                        'data' : {
1016                            id : id
1017                        },
1018                        'url' : datatill.url('customers/ajax_get'),
1019                        'quietMillis' : 250,
1020                        'success' : function(data,xhr) {
1021                            callback({
1022                                id : data.id,
1023                                text : data.accounting_code+'-'+data.customer_title
1024                            });
1025                        },
1026                        'error' : function(xhr) {
1027
1028                        }
1029
1030                    });
1031            }
1032        });
1033    };
1034   
1035
1036    $.toDecimal = function(number,negative,decimals,debug){
1037
1038        decimals= isNaN(decimals) ? 2 : decimals < 0 ? 2 : decimals;
1039        number=$.trim(number+'');
1040        number=number.replace(/\s/g,'').replace(/,/g,'.').replace(/(\.)([0-9]*)((\.)([0-9]*))/g,'$1$2').replace(/[^\-\.0-9]/,'');
1041        //if(!negative) number=number.replace('-',0);
1042
1043        if(number.indexOf('-')>0) number='0';
1044        number=+number;
1045        var a = Math.pow(10,decimals),b = Math.pow(10,decimals+1),c=b/a;
1046
1047        return isNaN(number)? (+0).toFixed(decimals) : (Math.round(Math.round(number*b)/c)/a).toFixed(decimals);
1048    };
1049    /**
1050     * Calls a bootbox modal for you to load your form into via JSON AJAX Request
1051     * Requires: data.title for header & data.message
1052     * @param {[object]} args
1053     * @param args.url          Url to GET form and POST/PUT data
1054     * @param args.params       Extra params for GET/PUT/POST besides form
1055     * @param args.successCallback      Custom Callback for clicking the submit button. handling form etc
1056     * @param args.title        An Optional Title for your modal
1057     * @param args.message      A Custom Message for your modal
1058     * @usage var popup = new $.Crud({ url: datatill.url(), params: {} }).create()
1059     */
1060    $.Crud = function(args){
1061
1062        var args = $.extend({
1063            url :'',
1064            params : {},
1065            onHidden : null,
1066            successCallback : null,
1067            title : '',
1068            message : ''
1069        }
1069,args);
1070
1071        this.args = args;
1072        this.success = false;
1073        this.id = null;
1074        this.dialog = null;
1075    };
1076
1077    $.Crud.prototype = {
1078        create : function(args){
1079            var args = $.extend(true,{},{
1080                buttons :
1081                {
1082                    success : {
1083                        label : 'Create',
1084                        className : 'btn btn-success btn-save',
1085                    }
1086                }
1087            },args);
1088
1089
1090            var form = this.fetchForm(args);
1091        },
1092        read : function(args) {
1093            // Todo
1094        },
1095        update : function(args){
1096            var args = $.extend(true,{},{
1097                buttons :
1098                {
1099                    success : {
1100                        label : 'Update',
1101                        className : 'btn btn-success btn-save',
1102                    }
1103                }
1104            },args);
1105            var form = this.fetchForm(args);
1106        },
1107        delete : function(args) {
1108            var self = this;
1109
1110            var dialog = self.createDialog({
1111                    title : (!self.args.title.length ? 'Please confirm before proceeding.' : self.title),
1112                    message : (!self.args.message.length ? 'Are you sure you want to delete this item ?' : self.message),
1113                    buttons : {
1114                        success : {
1115                            label : 'Delete',
1116                            className : 'btn btn-danger btn-save',
1117                            callback : function(ev) {
1118                                var form = $(ev.delegateTarget).find('.modal-content > .modal-body');
1119                                $.post(self.args.url,self.args.params,function(data){
1120                                    if(typeof data.success !== 'undefined' && data.success > 0) {
1121                                        self.showSuccess(form,'Item successfully deleted');
1122                                        self.success = data.success;
1123                                        self.id = data.success;
1124                                        return true;
1125                                    } else {
1126                                        self.success = false;
1127                                        bootbox.alert('An Error has occured trying to delete this item');
1128                                    }
1129                                });
1130                                return false;
1131                            }
1132                        },
1133                        danger : {
1134                            label : 'Cancel',
1135                            className : 'btn btn-primary pull-left',
1136                            callback : function(ev) {
1137                                return true;
1138                            }
1139                        }
1140                    }
1141                });
1142        },
1143        fetchForm : function(args) {
1144            var self = this;
1145            if(typeof self.args.message !== 'undefined' && self.args.message.length) {
1146            var args = $.extend({
1147                    title : (typeof self.args.title !== 'undefined' && self.args.title.length > 0 ? self.args.title : data.title ),
1148                    message : self.args.message
1149                },args);
1150                return self.createDialog(args);
1151            }
1152            var xhr = $.ajax({
1153                url : self.args.url,
1154                data : self.args.params,
1155                method : 'GET',
1156                contentType : 'JSON',
1157                success : function(data,xhr) {
1158                    self.createDialog({
1159                        title : (typeof self.args.title !== 'undefined' && self.args.title.length > 0 ? self.args.title : data.title ),
1160                        message : data.message || '',
1161                        buttons : args.buttons
1162                    });
1163                }
1164            });
1165        },
1166        postForm : function(form){
1167	        $('.btn-save').button('loading');
1168            var self = this;
1169            var data = form.serializeArray();
1170                if(typeof self.args.params !== 'undefined' && self.args.params !== null) {
1171                    data.push(self.args.params);
1172                }
1173            var xhr = $.ajax({
1174                url : self.args.url,
1175                data : data,
1176                method : 'POST',
1177                dataType : 'JSON',
1178                success : function(data,xhr) {
1179					$('.btn-save').button('reset');
1180                    if($.isPlainObject(data.errors) && !$.isEmptyObject(data.errors)) {
1181                        self.showErrors(form,data.errors);
1182                        self.success = false;
1183                    } else if(data.success > 0) {
1184                        // TODO showSuccess
1185                        self.hideErrors(form);
1186                        var msg = (typeof data.message !== 'undefined' && data.message !== null ? data.message : 'Your action was successfull');
1187                        self.showSuccess(form,msg);
1188                        self.success = data.success;
1189                        self.id = data.success;
1190                    }
1191                },
1192                error : function(xhr) {
1193					$('.btn-save').button('reset');
1194                    bootbox.alert('A problem has occured with your request, Please try again later.',function(resp){
1195                        bootbox.hideAll();
1196                    });
1197                }
1198            });
1199        },
1200        createDialog : function(args) {
1201            var self = this;
1202
1203            var args = $.extend(true,{},{
1204                title : '',
1205                message : '',
1206                buttons : {
1207                    success : {
1208                        label : 'Save',
1209                        className : 'btn btn-success btn-save',
1210                        callback : function(ev){
1211                            var form = $(ev.delegateTarget).find('form');
1212                            if(form.length > 0) {
1213                                self.hideErrors(form);
1214                                self.postForm(form);
1215                            }
1216                            return false;
1217                        }
1218                    },
1219                    danger : {
1220                        label : 'Close',
1221                        className : 'btn btn-danger pull-left',
1222                        callback : function(ev) {
1223                            return true;
1224                        }
1225                    }
1226                }
1227            },args);
1228
1229            // https://github.com/select2/select2/issues/1436
1230            $.fn.modal.Constructor.prototype.enforceFocus = function() {};
1231            // Fetch our Dialog for display
1232            var dialog = bootbox.dialog(args);
1233            dialog.on('hidden.bs.modal',function(e){
1234                // Remove all old select2 dom content
1235                $(e.currentTarget).find('.select-2').select2('destroy');
1236                if(e.target == this) {
1237
1238                    if($.isFunction(self.args.onHidden) && self.args.onHidden !== null) {
1239                        return self.args.onHidden(e,self);
1240                    }
1241                }
1242            });
1243            // Anything we should do once hidden ?
1244        },
1245        showSuccess : function(form,msg) {
1246			if (typeof do_background_tickets == 'function') { do_background_tickets();}
1247//             var alert = ['<div class="alert fade in alert-success form-success">',msg,' <br/>This Screen will automatically close in <b class="timer">3</b> seconds.</div>'].join('');
1248            this.success = true;
1249			form.closest('.modal').modal('hide');  // IRIRIR
1250/*
1251            form.prepend(alert);
1252            var timeout = 2000;
1253            var timer = setInterval(function(){
1254                form.find('.alert.form-success > .timer').text((timeout/1000));
1255                timeout = timeout-1000;
1256                if(timeout<0) {
1257                    clearInterval(timer);
1258                    timer=null;
1259                    form.closest('.modal').modal('hide');
1260                    return false;
1261                }
1262            },1000);
1263*/
1264
1265        },
1266        showErrors : function(form,errors) {
1267            var rows = [],formErrors=form.find('.alert-danger.form-errors');
1268            $.each(errors,function(i,val){
1269                rows.push('<li>'+val+'</li>');
1270            });
1271            formErrors.find('ul').html(rows.join(''));
1272            formErrors.show(330);
1273            this.success = false;
1274            return false;
1275        },
1276        hideErrors : function(form) {
1277            var formErrors = form.find('.alert-danger.form-errors');
1278            formErrors.hide(330).find('ul').html('');
1279            this.success = true;
1280            return true;
1281        }
1282    };
1283    window.datatill = $.extend(window.datatill || {},{
1284        init:function(){
1285            $('body').off('click.datatillInit');
1286            $('body').on('click.datatillInit','[data-dismiss=popup-destroy]',function(e){
1287                e.preventDefault();
1288                var popup=$(this).closest('.popup'),api=popup.data('popup');
1289                if(api)api.destroy=true;
1290                popup.popup('hide');
1291            });
1292            $('body').on('click.datatillInit','[data-dismiss=popup]',function(e){
1293                e.preventDefault();
1294                $(this).closest('.popup').popup('hide');
1295            });
1296            $('body').on('click.datatillInit','[data-toggle=popup-fullscreen]',function(e){
1297                e.preventDefault();
1298                var popup=$(this).closest('.popup');
1299                popup.toggleClass('fullscreen');
1300                $(window).resize();
1301            });
1302        }
1303    });
1304})(jQuery);
1305
1306
1307if (typeof(bootbox) != "undefined") {
1308    bootbox.setDefaults({
1309        show: true,
1310        backdrop: true,
1311        closeButton: true,
1312        animate: false,
1313        className: "my-modal"
1314    });
1315}
1316
1317
1318if (typeof($.fn.dataTable) != "undefined") {
1319	if (typeof($.fn.dataTable.defaults) != "undefined") {
1320		$.extend($.fn.dataTable.defaults, {
1321		  "fnInitComplete": function(oSettings, json) {
1322			$(".dataTables_length  select").select2({
1323				minimumResultsForSearch: -1,
1324				dropdownAutoWidth : true
1325			});
1326		  },
1327		});
1328	}
1329	if (typeof($.fn.dataTableExt.oApi) != "undefined") {
1330		$.extend($.fn.dataTableExt.oApi, {
1331		  "fnMultiFilter": function(oSettings, oData) {
1332			    for ( var key in oData )
1333			    {
1334			        if ( oData.hasOwnProperty(key) )
1335			        {
1336			            for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
1337			            {
1338			                if( oSettings.aoColumns[i].idx == key )
1339			                {
1340			                    oSettings.aoPreSearchCols[ i ].sSearch = oData[key];
1341			                    break;
1342			                }
1343			            }
1344			        }
1345			    }
1346			    this.oApi._fnReDraw( oSettings );
1347		  },
1348		});
1349	}
1350}
1351
1352function initialize_gallery(){
1353	$('a.DTgallery').featherlightGallery({
1354		previousIcon: '&#9664;',     /* Code that is used as previous icon */
1355		nextIcon: '&#9654;',         /* Code that is used as next icon */
1356		galleryFadeIn: 100,          /* fadeIn speed when slide is loaded */
1357		galleryFadeOut: 300          /* fadeOut speed before slide is loaded */
1358	});
1359}
1360
1361function b64Encode(str) {
1362    if (typeof str != 'string') throw new TypeError('Passed parameter is not a string');
1363    const output = str.replace(
1364        /[\u0080-\u07ff]/g,  // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
1365        function(c) {
1366            var cc = c.charCodeAt(0);
1367            return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); }
1368    ).replace(
1369        /[\u0800-\uffff]/g,  // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
1370        function(c) {
1371            var cc = c.charCodeAt(0);
1372            return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); }
1373    );
1374    return btoa(output);
1375}
1376
1377function b64Decode(str) {
1378    if (typeof str != 'string') throw new TypeError('Passed parameter is not a string');
1379    var base64regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;
1380    if (base64regex.test(str)) { str = atob(str); } // only does a decode if the string is a valid Base64 encoded
1381    // note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
1382    const output = str.replace(
1383        /[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,  // 3-byte chars
1384        function(c) {  // (note parentheses for precedence)
1385            var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f);
1386            return String.fromCharCode(cc); }
1387    ).replace(
1388        /[\u00c0-\u00df][\u0080-\u00bf]/g,                 // 2-byte chars
1389        function(c) {  // (note parentheses for precedence)
1390            var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f;
1391            return String.fromCharCode(cc); }
1392    );
1393    return output;
1394}
1395
1396var file_uploads_in_progress = [] ;
1397function get_new_upload_id(){
1398    var upload_id = Math.random();
1399    return upload_id;
1400}
1401
1402function begin_upload(upload_id) {
1403
1404    var upload = {
1405        id : upload_id,
1406        complete : false
1407    };
1408    file_uploads_in_progress.push(upload);
1409    return upload_id;
1410}
1411
1412function complete_upload(upload_id) {
1413    var len = file_uploads_in_progress.length;
1414    for (var i=0; i<len;i++){
1415        if (file_uploads_in_progress[i].id == upload_id){
1416            file_uploads_in_progress[i].complete = true;
1417            break;
1418        }
1419    }
1420}
1421
1422function uploads_complete(){
1423    var len = file_uploads_in_progress.length;
1424    for (var i=0; i<len;i++){
1425        if (file_uploads_in_progress[i].complete == false){
1426            return false;
1427        }
1428    }
1429    return true;
1430}
1431
1432
1433// MAR-19
1434function check_refer_a_friend_item(message, selector){
1435    $(selector).hide();
1436    var theOption = [];
1437    $('#table_lines tr').each(function(i){
1438        if( $(this).css('display') !== 'none') {
1439            var id_split = $(this).attr('id').split('_');
1440            theOption.push($('#item_type_' + id_split[1] + ' option:selected').val());
1441        }
1442    });
1443    if ( theOption.indexOf('BANDWIDTH') == -1 ) {
1444        $(selector).html(message).show();
1445        return;
1446    }
1447    return 1;
1448}
1449 
1450function generate_label(label_list_color,label_list_name){
1451	return "<button type='button' class='label label-default nopointevent' style='border-color:"+label_list_color+"; margin-bottom:2px; border-left: 15px solid "+label_list_color+"; '>"+label_list_name+"</button> ";
1452}
1453 
1454// Format a number with thousand markers and fixed decimals
1455function formatNumber(number, dec_f, dec_m, thou_m) {
1456  var dec_f = isNaN(dec_f = Math.abs(dec_f)) ? 2 : dec_f,
1457    dec_m = dec_m == undefined ? "." : dec_m,
1458    thou_m = thou_m == undefined ? "," : thou_m,
1459    neg = number < 0 ? "-" : "",
1460    actual = String(parseInt(number = Math.abs(Number(number) || 0).toFixed(dec_f))),
1461    split = (split = actual.length) > 3 ? split % 3 : 0;
1462
1463  return neg + (split ? actual.substr(0, split) + thou_m : "") + 
1464  actual.substr(split).replace(/(\d{3})(?=\d)/g, "$1" + thou_m) + 
1465  (dec_f ? dec_m + Math.abs(number - actual).toFixed(dec_f).slice(2) : "");
1466};
1467
1468// Pass a decimal representation to have it converted to D:HH:MM:SS
1469function convertNumberToTimeDHMS(value) {
1470    var days = Math.floor(value/1440);
1471    var sub_days = Boolean(days) ? value - (1440*days) : value;
1472    var show_hours = Boolean(days) ? 1 : 0;
1473    var hours = Math.floor(sub_days/60);
1474    var hour_ = (String(hours).length == 1) ? '0'+hours : hours;
1475    var sub_hour = Boolean(hours) ? sub_days - (60*hours) : sub_days;
1476    var minutes = Math.floor(sub_hour);
1477    var seconds = Math.round((sub_hour % 1)*100)/100;
1478    var min = ((String(minutes).length == 1) ? '0'+minutes : minutes)
1479    var sec_ = parseInt(seconds*60);
1480    var sec = (String(sec_).length == 1) ? '0'+sec_ : sec_;
1481    return ((Boolean(days)) ? days + ':' : '' ) + ((Boolean(hours) || Boolean(show_hours)) ? hour_ + ':' : '' ) + min + ':' + sec;
1482}
1483
1484// Pass a decimal representation to have it converted to MM:SS
1485function convertNumberToTimeMS(value) {
1486    var minutes = Math.floor(value / 60);
1487    var seconds = Math.round((value % 60) * 100) / 100;
1488    var min = ((String(minutes).length === 1) ? '0' + minutes : minutes);
1489    var sec_ = parseInt(seconds);
1490    var sec = (String(sec_).length == 1) ? '0' + sec_ : sec_;
1491    return min + ':' + sec;
1492}
1493
1494/**
1495 *  `DTSUPPORT ONLY` will be sections in the system
1496 *  that allow only the namesake user additional functionality;
1497 *  debugging, insights, raw data, testing ...
1498 */
1499function dtsupportInsertMarker() {
1500    $(".dtsupport_only").each(function(){
1501        $(this).before("<hr>
1501").prepend($('<div>').text("DTSUPPORT ONLY"));
1502    });
1503}
1504
1505var customjs_version=15;

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.