bitwrench 1.2.13 → 1.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3202 @@
1
+ export let bw = (()=>{
2
+
3
+ var bw = {};
4
+ bw.exportName = "bw"; //
5
+ bw.exportModuleType = "AMD"; //default export see UMD wrapper above for more info this. it allows the consumer to know how this was loaded.
6
+
7
+
8
+ // ===================================================================================
9
+ bw.choice = function (x,choices,def) {
10
+ /**
11
+ bw.choice(x,choices-dictionary, default)
12
+
13
+
14
+ Allows a dictionary to be used as a switch statement, including functions.
15
+
16
+ example:
17
+ colors = {"red": 1, "blue": 2, "aqua" : function(z){return z+"marine"}};
18
+ bw.choice("red",colors,"0") ==> "1"
19
+ bw.choice("shiny",colors,"0") ==> "0"
20
+ bw.choice("aqua",colors) ==> "aquamarine"
21
+ */
22
+ var z = (x in choices) ? choices[x] : def;
23
+ return _to(z) == "function" ? z(x) : z;
24
+ };
25
+
26
+
27
+ // ===================================================================================
28
+ bw.jsonClone = function (x) {
29
+ /**
30
+ bw.jsonClone(object)
31
+
32
+ crude deep copy by value of an object as long as no js dates or functions
33
+ */
34
+ return JSON.parse(JSON.stringify(x));
35
+ };
36
+
37
+
38
+ // ===================================================================================
39
+ bw.typeOf = function (x, baseTypeOnly) {
40
+ /**
41
+ _to(x, baseTypeOnly) returns a useful typeOf the object.
42
+
43
+ _to(2) // "number"
44
+ bw.typeof( function(){}) // "function"
45
+
46
+ function Car(make, model, year) {
47
+ this.make = make;
48
+ this.model = model;
49
+ this.year = year;
50
+ }
51
+
52
+ x = new Car("Ford", "Escape", 2009);
53
+
54
+ _to(Car) // "function"
55
+ _to(x) // "Car" ---> returns correct object type
56
+ _to(x,true) // "object" ---> returns base object type
57
+
58
+ */
59
+
60
+ //A useable typeof operator. See this fantastic reference for a starter
61
+ //https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
62
+
63
+ if (x === null)
64
+ return "null";
65
+
66
+ var y = (typeof x == "undefined") ? "undefined" : (({ /*empty*/}).toString.call(x).match(/\s([a-zA-Z]+)/)[1].toLocaleLowerCase());
67
+
68
+ if ((y != "object") && (y != "function"))
69
+ return y;
70
+ if (baseTypeOnly == true) // so if undefind or anything but true
71
+ return y;
72
+
73
+ var r = y;
74
+ try {
75
+ r = (x.constructor.name.toLocaleLowerCase() == y.toLocaleLowerCase()) ? y : x.constructor.name; // return object's name e.g.
76
+ }
77
+ catch (e) {/*empty*/}
78
+ if (r == "object") {
79
+ if (x["_is_BW_HTMLNode"] == true )
80
+ r="BW_HTMLNode";
81
+ }
82
+ return r;
83
+ };
84
+
85
+ var _to = bw.typeOf;
86
+ bw.to = _to;
87
+ //===============================================
88
+ // internally used type check and assign function
89
+ bw.typeAssign = function (a, typeString, trueValue, falseValue) {
90
+ /**
91
+ bw.typeAssign(variable, typeString, trueValue, falseValue)
92
+ typeAssign is used to see if the argument a is of type typeString as defined by _to().
93
+ if it is then trueValue is returned else falseValue.
94
+
95
+ bw.typeAssign("23","number","is a number!", "not a number!") ==> "is a number!"
96
+ bw.typeAssign([23],"number","is a number!", "not a number!") ==> "not a number!" // is an array of length 1
97
+
98
+ can also supply list of types
99
+ bw.typeAssign(23,["string","number"], "string or num", "something else") ==> "string or num"
100
+ bw.typeAssign(true,["string","number"], "string or num", "something else") ==> "something else"
101
+ */
102
+ if (["string","array"].indexOf(_to(typeString)) == -1) // typeString must be a string or an arrag or strings
103
+ typeString = "notValidType";
104
+
105
+ if (_to(typeString) == "string")
106
+ typeString = [typeString];
107
+
108
+ return (typeString.indexOf(_to(a)) >= 0) ? trueValue : falseValue;
109
+ };
110
+
111
+ var _toa = bw.toa = bw.typeAssign; // eslint-disable-line no-unused-vars
112
+
113
+
114
+ //===============================================
115
+ // internally used type check and assign function with functional support (trueValue or falseValue can be functions which are passed the param a)
116
+
117
+ bw.typeConvert = function (a, typeString, trueValue, falseValue) {
118
+ /**
119
+ bw.typeConvert(variable, typeString, trueValue, falseValue)
120
+ typeConvert is used to see if the argument a is of type typeString as defined by _to().
121
+ if it is then trueValue is returned else falseValue.
122
+
123
+ bw.typeConvert("23","number","is a number!", "not a number!") ==> "is a number!"
124
+ bw.typeConvert([23],"number","is a number!", "not a number!") ==> "not a number!" // is an array of length 1
125
+
126
+ can also supply list of types
127
+ bw.typeConvert(23,["string","number"], "string or num", "something else") ==> "string or num"
128
+ bw.typeConvert(true,["string","number"], "string or num", "something else") ==> "something else"
129
+
130
+ bw.typeConvert(23,["string","number"],function(x){return x+1},function(){return function(x){return x+2}})}) ==> 24
131
+
132
+ bw.typeConvert(23,["string"],function(x){return x+1},function(){return function(x){return x+2;}})}) ==> function(x){return x+2;}
133
+
134
+ however typeConvert also allows functions (as apposed to typeAssign)
135
+ */
136
+ if (["string","array"].indexOf(_to(typeString)) == -1) // typeString must be a string or an arrag or strings
137
+ typeString = "notValidType";
138
+
139
+ if (_to(typeString) == "string")
140
+ typeString = [typeString];
141
+
142
+ return (typeString.indexOf(_to(a)) >= 0) ? (_to(trueValue) == "function") ? trueValue(a) : trueValue : ( _to(falseValue) == "function") ? falseValue(a): falseValue;
143
+ };
144
+ //var _tc = bw.typeConvert;
145
+ var _toc = bw.tc = bw.typeConvert; // eslint-disable-line no-unused-vars
146
+ //===============================================
147
+ // internally used function for options copy
148
+ // keys in opts are copied to dopts (or overwrite options in dopts)
149
+ /* istanbul ignore next */
150
+ var optsCopy = function(dopts,opts) {
151
+ /* istanbul ignore next */
152
+ if ((_to(opts) == "object") && (_to(dopts)=="object")) {
153
+ var i;
154
+ for (i in opts) {
155
+ if ((_to(opts[i]) == "object" )||(_to(dopts[i])=="object")) {
156
+ var j;
157
+ for (j in opts[i])
158
+ dopts[i][j] = opts[i][j];
159
+ }
160
+ else
161
+ dopts[i] = opts[i];
162
+ }
163
+ }
164
+ return dopts;
165
+ };
166
+ bw._oc = optsCopy;
167
+ // ===================================================================================
168
+ bw.arrayUniq = function (x){
169
+ /**
170
+ arrayUniq(x)
171
+ returns uniq elements of simple array x.
172
+ */
173
+ if (_to(x) != "array")
174
+ return [];
175
+ return x.filter (function (v, i, arr) {return (arr.indexOf(v)==i);});
176
+ };
177
+ // ===================================================================================
178
+ bw.arrayBinA = function (a,b) {
179
+ /**
180
+ arrayBinA(x)
181
+ returns intersection elements of to simple arrays a and b
182
+ */
183
+ if ((_to(a)!="array") || (_to(b)!== "array"))
184
+ return [];
185
+ return bw.arrayUniq(a.filter(function(n) { return b.indexOf(n) !== -1;}));
186
+ };
187
+
188
+ bw.arrayBNotInA = function (a,b) {
189
+ /**
190
+ arrayBNotinA(x)
191
+ returns elements of b not present in a
192
+ */
193
+ if ((_to(a)!="array") || (_to(b)!== "array"))
194
+ return [];
195
+ return bw.arrayUniq(b.filter(function(n) { return a.indexOf(n) < 0;}));
196
+ };
197
+
198
+ //===============================================
199
+ /* istanbul ignore next */
200
+ bw.DOMIsElement = function(el) {
201
+ /**
202
+ @method bw.DOMIsElement() - returns whether a supplied element is a HTML DOM element. only useful in browser,
203
+ */
204
+ var r = false;
205
+ try {
206
+ if(_to(el)== "undefined")
207
+ return r;
208
+ if ((bw.isNodeJS() == false) || (typeof Element == "function"))
209
+ r = el instanceof Element;
210
+
211
+ }
212
+ catch(e) {
213
+ r = (typeof HTMLElement === "object" ? el instanceof HTMLElement : //DOM2
214
+ el && (typeof el === "object") && (el !== null) && (el.nodeType === 1) && (typeof el.nodeName==="string")
215
+ );
216
+ bw.logd(e.toString());
217
+ }
218
+ return r;
219
+ };
220
+
221
+ var _isEl = bw.DOMIsElement;
222
+
223
+ //===============================================
224
+ /* istanbul ignore next */
225
+ bw.DOMGetElements = function (el, type) {
226
+ /**
227
+ @method DOMGetElements(el, type) returns an array of DOM elements (if running in browser)
228
+
229
+ @param {string | DOM_node} el - if string uses CSS selector other wise if already a DOM element returns itself
230
+ @return an js array of zero or more matching DOM nodes
231
+
232
+ DOMGetElements always looks in the root.
233
+
234
+ */
235
+
236
+ /*
237
+ TODO:
238
+ var container = document.querySelector("#test");
239
+ var matches = container.querySelectorAll("div.highlighted > p");
240
+
241
+ */
242
+ var r=[],a=[],i;
243
+
244
+ if (bw.isNodeJS() == false)
245
+ { // we're running in a browser
246
+ if (_isEl(el))
247
+ return [el];
248
+ if (_to(el) == "string") { // now its a string so we have choices..
249
+ type = _toa(type,"string",type,"auto"); // auto means detect whether has a # or . in front of it
250
+ el.trim();
251
+ if (type == "auto")
252
+ type = bw.choice(el[0],{".":"className", "#":"id"},"tagName");
253
+ type=type.toLowerCase();
254
+ switch (type) {
255
+ case "id" : //get Element by ID
256
+ el = (el[0]=="#") ? el.substring(1,el.length) : el;
257
+ a = document.getElementById(el);
258
+ a = _toa(a,"null",[],[a]);
259
+ break;
260
+ case "classname": // get Elements by class name
261
+ el = (el[0]==".") ? el.substring(1,el.length) : el;
262
+ a = document.getElementsByClassName(el);
263
+ break;
264
+ case "tagname" : // get Elements by tag name
265
+ a = document.getElementsByTagName(el);
266
+ break;
267
+ case "name":
268
+ a = document.getElementsByName(el);
269
+ break;
270
+ case "css" :
271
+ a = document.querySelectorAll(el);
272
+ break;
273
+ default:
274
+ a = document.querySelectorAll(el);
275
+
276
+ }
277
+ for (i in a)
278
+ r.push(a[i]);
279
+ }
280
+ }
281
+
282
+ return r.filter(function(x){return _isEl(x);});
283
+ };
284
+ //var _els = bw.DOMGetElements;
285
+
286
+ // =============================================================================================
287
+ /* istanbul ignore next */
288
+ bw.DOMSetElements = function(domElement,param) {
289
+ /**
290
+ @method DOMSetElements(domElement, param) sets DOM elements with the supplied (optional) params
291
+
292
+ @param {string | array | dict |function} - params to set on DOMElements
293
+ @return an js array of zero or more matching DOM nodes
294
+ */
295
+
296
+ var els = bw.DOMGetElements(domElement);
297
+ if (els==[])
298
+ bw.log("dom element not found");
299
+
300
+ var i,l,e, ef = function(x,p){bw.log(x,p);};
301
+ for (l=0; l<els.length; l++) {
302
+ e = els[l];
303
+ switch(_to(param)) {
304
+ case "array":
305
+ try{
306
+ for (i=0; i<param.length; i++) e[param[i][0]] = param[i][1];
307
+ }
308
+ catch(d) {ef(d,param);}
309
+ break;
310
+ case "object":
311
+ try {
312
+ for (i in param) e[i] = param[i];
313
+ }
314
+ catch(d) {ef(d,param);}
315
+ break;
316
+ case "string":
317
+ try {
318
+ e.innerHTML = param;
319
+ }
320
+ catch(d) {ef(d,param);}
321
+ break;
322
+ case "function":
323
+ try {
324
+ param(e); // apply a function to e
325
+ }
326
+ catch(d) {ef(d,param);}
327
+ break;
328
+ default: break;
329
+ }
330
+ }
331
+
332
+ return els;
333
+ };
334
+
335
+ bw.DOM = bw.DOMSetElements; //short hand
336
+ //================================================================================
337
+ bw.DOMInsertElement = function (parentEl, htmldata, putFirst) {
338
+ /**
339
+ DOMInsertElement (attachEl, html , putFirst)
340
+ creates an HTML element (browser only). If an attachment element is provided it will attach the new element to the attachElement.
341
+ if putFirst == true it is made the first child of the attachEl else it is the lastChild of the attachEl
342
+ */
343
+ var el = null;
344
+ if (bw.isNodeJS() == false) {
345
+ if (bw.DOMIsElement(htmldata))
346
+ el = htmldata;
347
+ else {
348
+ el = document.createElement("div"); //outer wrapper
349
+ el.innerHTML = bw.html(htmldata);
350
+ el = el.firstChild; // get our element back
351
+ }
352
+ if (parentEl) {
353
+ parentEl = bw.DOM(parentEl)[0];
354
+ if (putFirst ) {
355
+ parentEl.insertBefore(el, parentEl.firstChild); // put it first
356
+ }
357
+ else
358
+ parentEl.appendChild(el); // put it last
359
+ }
360
+ }
361
+ return el;
362
+ };
363
+ bw.DOMIns = bw.DOMInsertElement;
364
+
365
+ // =============================================================================================
366
+ bw.htmlToElement = function (htmldata) {
367
+ var el=null;
368
+ if (bw.isNodeJS() == false) {
369
+ if (bw.DOMIsElement(htmldata))
370
+ el = htmldata;
371
+ else {
372
+ el = document.createElement("div"); //outer wrapper
373
+ el.innerHTML = bw.html(htmldata);
374
+ el = el.firstChild; // get our element back
375
+ }
376
+ }
377
+ return el;
378
+ };
379
+ // =============================================================================================
380
+
381
+ bw.DOMReplaceElement = function(oldEl, newEl) {
382
+ if (bw.isNodeJS() == false) {
383
+ var e = bw.DOM(oldEl)[0];
384
+ return e.parentNode.replaceChild(bw.htmlToElement(newEl), e);
385
+ }
386
+ return null;
387
+ };
388
+ // =============================================================================================
389
+ /**
390
+ bitwrench: color functions (used for theming and interpolations)
391
+
392
+ bitwrench color functons operate using this internal color representation model:
393
+ [c0, c1, c2, alpha, model]
394
+ where c0, c1, c2 are model dependant
395
+ alpha represents the transperancy
396
+ model is a color model string (lowercase) "rgb", or "hsl" (compatible with HTML/CSS colors)
397
+
398
+ colorParse() ==> take an input color of anymodel and output a bw [c0,c1,c2,a,m] array
399
+ */
400
+ bw.colorInterp = function(x, in0, in1, colors, stretch) {
401
+ /**
402
+ @method colorInterp (x, lo, hi, colors[], stretch) - interpolate between and array of colors.
403
+ x is a number between the numbers in0 <= x <= in1
404
+ colors is an array of colors supplied in rgb format e.g. ["#123", "#234"]
405
+ colors can be anylength
406
+ */
407
+ var c = _toa(colors,"array",colors,["#000","#fff"]); // make sure we have an array of colors
408
+ c = c.length == 0 ? ["#000","#fff"] : c; // no colors provide .. interp grayscale is default
409
+ if (c.length == 1)
410
+ return c[0];
411
+ //ok now we we have an array of atleast length 2 which hopefully contains colors.
412
+ c = c.map(function(x){return bw.colorParse(x);}); // all colors will now be converted to bw RGB format
413
+ var a = bw.mapScale(x,in0,in1,0,c.length-1,{clip: true, expScale: stretch});
414
+ var i = bw.clip(Math.floor(a),0,c.length-2);
415
+ var r = a-i;
416
+ var _f = function(x) {return bw.mapScale(r,0,1, c[i][x],c[i+1][x],{clip:true});};
417
+ return [_f(0), _f(1), _f(2),_f(3),"rgb"];
418
+
419
+ };
420
+
421
+
422
+ // =============================================================================================
423
+ bw.colorHslToRgb = function (h, s, l, a, rnd){
424
+ /**
425
+ @method colorHslToRgb
426
+ Converts an HSL color value to RGB. Conversion formula
427
+
428
+ Assumes h is [0..360] , s, and l are contained in the set [0 .. 100].
429
+ returns r, g, and b in the set [0, 255].
430
+
431
+ @param {number} h The hue [0..360]
432
+ @param {number} s The saturation [0..100]
433
+ @param {number} l The lightness [0..100]
434
+
435
+ OR...
436
+
437
+ pass the colors as a bitwrench color array as a single parameter:
438
+
439
+ colorHslToRgb([h,s,l,a,"hsl"])
440
+
441
+ @return {Array} The RGB representation as [r, g, b, alpha, "rgb"]
442
+
443
+ last parameter rnd rounds the results to 0..255. set to false to eliminate rounding. This can be useful for chained calcs
444
+
445
+ see : adapted from http://hsl2rgb.nichabi.com/javascript-function.php
446
+
447
+ */
448
+ if (_to(h)=="array") { // handles colors of [h,s,l,a,"hsl"]
449
+ s=h[1];
450
+ l=h[2];
451
+ a=h[3];
452
+ h=h[0]; //do this last so it doesn't overwrite iself
453
+ }
454
+ var _fn = rnd == false ? function(x){return x;} : function(x){return bw.clip(Math.round(x),0,255);} ;
455
+
456
+ var r,g,b,c,x,m;
457
+ h = (h+360)%360;
458
+ h /= 60;
459
+ if (h < 0) h = 6 - (-h % 6);
460
+ h %= 6;
461
+
462
+ s = Math.max(0, Math.min(1, s / 100));
463
+ l = Math.max(0, Math.min(1, l / 100));
464
+
465
+ c = (1 - Math.abs((2 * l) - 1)) * s;
466
+ x = c * (1 - Math.abs((h % 2) - 1));
467
+
468
+ if (h < 1) {
469
+ r = c; g = x; b = 0;
470
+ } else if (h < 2) {
471
+ r = x; g = c; b = 0;
472
+ } else if (h < 3) {
473
+ r = 0; g = c; b = x;
474
+ } else if (h < 4) {
475
+ r = 0; g = x; b = c;
476
+ } else if (h < 5) {
477
+ r = x; g = 0; b = c;
478
+ } else {
479
+ r = c; g = 0; b = x;
480
+ }
481
+
482
+ m = l - c / 2;
483
+ r = (r + m) * 255;
484
+ g = (g + m) * 255;
485
+ b = (b + m) * 255;
486
+ return [_fn(r),_fn(g),_fn(b),a,"rgb"];
487
+ };
488
+
489
+ // =============================================================================================
490
+ bw.colorRgbToHsl = function (r, g, b, a, rnd) {
491
+ /**
492
+ Converts an RGB color value to HSL. Conversion formula
493
+ adapted from http://en.wikipedia.org/wiki/HSL_color_space.
494
+ Assumes r, g, and b are contained in the set [0, 255] and
495
+ returns h as [0..360] s, and l in the set [0 .. 100].
496
+
497
+ @param {number} r The red color value
498
+ @param {number} g The green color value
499
+ @param {number} b The blue color value
500
+
501
+ pass the colors as a bitwrench color array as a single parameter:
502
+ colorRgbToHsl([h,s,l,a,"rgb"])
503
+
504
+ last parameter rnd rounds the results to 0..255. set to false to eliminate rounding. This can be useful for chained calcs
505
+ @return {Array} The HSL representation
506
+ */
507
+ if (_to(r)=="array") { // handles colors of [h,s,l,a,"hsl"]
508
+ g=r[1];
509
+ b=r[2];
510
+ a=r[3];
511
+ r=r[0]; //do this last so it doesn't overwrite iself
512
+ }
513
+
514
+ r /= 255, g /= 255, b /= 255;
515
+ var max = Math.max(r, g, b), min = Math.min(r, g, b);
516
+ var h, s, l = (max + min) / 2;
517
+
518
+ if(max == min){
519
+ h = s = 0; // achromatic
520
+ }else{
521
+ var d = max - min;
522
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
523
+ switch(max){
524
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
525
+ case g: h = (b - r) / d + 2; break;
526
+ case b: h = (r - g) / d + 4; break;
527
+ }
528
+ h /= 6;
529
+ }
530
+ var _fn = rnd == false ? function (x){return x;} : function(x){return Math.round(x);} ;
531
+ return [_fn(h*360), _fn(s*100), _fn(l*100), a, "hsl"];
532
+ };
533
+
534
+ // =============================================================================================
535
+
536
+ bw.colorParse = function(s,defAlpha) {
537
+ /**
538
+ @method bw.colorParse(s)
539
+
540
+ @description take a valid CSS style color string: #rgb | #rgba | #rrggbb | #rrggbbaa | rgb(r,g,b) | rgb(r,g,b,a) | hsl(h,s,l) | hsla(h,s,l,a )
541
+ ... and return array [c0,c1,c2,a,model] where model is one of "rgb", "hsl"
542
+ */
543
+ defAlpha = _toa(defAlpha,"number",defAlpha,255);
544
+ var r = [0,0,0,defAlpha,"rgb"]; // always return a valid type
545
+ if (_to(s)=="array"){ // it could be a bwcolor type [c0,c1,c2,a,model]
546
+ var p,df = [0,0,0,255,"rgb"];
547
+ for (p=0; p< s.length; p++)
548
+ df[p]=s[p];
549
+ s= String(df[4])+"("+String(df[0])+","+String(df[1])+","+String(df[2])+","+String(df[3])+")"; //could use slice..join(",")
550
+ }
551
+
552
+ s = String(s).replace(/\s/g,"");
553
+ var reT = /\s*(#|hsl|rgb|yuv|hsv){1}([a-f|A-F|0-9|,().\t ]*)/img;
554
+ var i,j=0,x = reT.exec(s);
555
+ if (_to(x)=="array" && (x.length >= 3)) {
556
+ r[4]= x[1] == "#" ? "rgb" : x;
557
+ if (x[1] == "#") { //parse one of these #rgb #rgba #rrggbb #rrggbbaa
558
+ switch (x[2].length) {
559
+ case 3: //#rgb
560
+ case 4: //#rgba
561
+ for (i=0; i< x[2].length; i++)
562
+ r[i] = parseInt(x[2][i]+x[2][i],16);
563
+ break;
564
+ case 6: //#rrggbb
565
+ case 8: //#rrggbbaa
566
+ for (i=0; i< x[2].length; i+=2)
567
+ r[j++] = parseInt(x[2][i]+x[2][i+1],16);
568
+ break;
569
+ default:
570
+ bw.logd("bw.parseColor bad input "+ s);
571
+ }
572
+ }
573
+ else { // its should be of form (c0,c1,c2) or (c0,c1,c2,alpha)
574
+ r[4] = x[1].toLocaleLowerCase();
575
+ if ((x[2][0] == "(") && (x[2][x[2].length-1] == ")")) { // parans are present
576
+ var v = x[2].substring(1,x[2].length-1);
577
+ v = v.split(",");
578
+ switch(v.length){ // valid entries are 3 or 4 components
579
+ case 3:
580
+ case 4:
581
+ for (i=0; i< v.length; i++)
582
+ r[i] = Number(v[i]);
583
+ break;
584
+ default:
585
+ bw.logd("bw.parseColor bad input : " + s);
586
+ }
587
+ } else {
588
+ bw.logd("bw.parseColor bad input : " + s);
589
+ }
590
+ }
591
+ }
592
+ return r;
593
+ };
594
+
595
+ // =============================================================================================
596
+ bw.colorToRGBHex = function(c, format) {
597
+ /**
598
+ @method bw.colorToRGBHex(color)
599
+ @description take a color of the form a string or [c0,c1,c2,alpha,model] ==> convert to #rrggbbaa format
600
+ format (optional) can be set to auto in which case alpha is ommitted if set to 255
601
+ */
602
+ var r = "#00000000";
603
+ var ph = function(x){var y=(bw.clip(Math.round(x),0,255)).toString(16); return (y.length==1)?"0"+y:y;}; // pad hex
604
+ c = bw.colorParse(c); // converts color to bw color vector format
605
+ switch(c[4]) {
606
+ case "rgb":
607
+ r = "#"+ph(c[0])+ph(c[1])+ph(c[2]);
608
+ if (!((format == "auto") && (c[3]==255)))
609
+ r += ph(c[3]);
610
+ break;
611
+ case "hsl":
612
+ r= bw.colorToRGBHex(bw.colorHslToRgb(c));
613
+ break;
614
+ default:
615
+ bw.logd("colorToRGBHex : unsupported format" + c[4]);
616
+ }
617
+ return r; // default
618
+ };
619
+ // =============================================================================================
620
+ bw.colorConvertColorSpace = function(c, space, rnd) {
621
+ /**
622
+ @method bw.colorConvertColorSpace(color, spaceToConvertTo)
623
+ @description take a color and convert it to the destination color space ("rgb" | "hsl")
624
+ color can be any valid color type ("#abc" | "hs(...)" or [r,g,b,a,"rgb"] etc)
625
+
626
+ optional 3rd param rnd if set to false will suppress rounding in calcs to allow chained color conversion w/o loss of precision
627
+
628
+ */
629
+ c = bw.colorParse(c);
630
+ if (space == c[4])
631
+ return c;
632
+
633
+ switch(c[4]) {
634
+ case "rgb":
635
+ break;
636
+ case "hsl":
637
+ c = bw.colorHslToRgb(c[0],c[1],c[2],c[3],rnd); // turns off rounding
638
+ break;
639
+ default:
640
+ bw.logd("colorConvertColorSpace: unsupported color format");
641
+ }
642
+ //now c is in the rgb space
643
+
644
+ switch(space) {
645
+ case "rgb":
646
+ break;
647
+ case "hsl":
648
+ c = bw.colorRgbToHsl(c[0],c[1],c[2],c[3],rnd); // turns off rounding
649
+ break;
650
+ default:
651
+ bw.logd("colorConvertColorSpace: unsupported color format");
652
+ }
653
+ return c;
654
+ };
655
+
656
+ // =============================================================================================
657
+ var _logdata=[];
658
+
659
+ bw.log = function (value,msg,opts) {
660
+ /**
661
+ bw.log(value, message, options)
662
+ write a value to the in-memory log
663
+ options {
664
+ clear: false | true | "clear-only"
665
+ false : normal write
666
+ true : clear log and add 1st entry
667
+ clear-only - only clear don't write, value, msg
668
+ saveMethod: "raw" | "JSON" // raw is default, save object as passed, JSON saves stringified version (useful for exporting or saving state)
669
+ }
670
+ */
671
+ var dopts = {
672
+ clear : false, // values fales, true, "clear-only"
673
+ saveMethod: "raw" // else "JSON"
674
+ };
675
+
676
+ dopts = optsCopy(dopts,opts);
677
+ if ((dopts["clear"] == true) || (dopts["clear"] == "clear-only")) {
678
+ _logdata = [["Time-stamp (ms)"," Value "," Message "]];
679
+ var ct = (new Date());
680
+ _logdata.push ([0, ct.getTime()," log started at " + ct.toString()]);
681
+ }
682
+
683
+ msg = _toa(msg,"undefined","",String(msg));
684
+ value = (dopts["saveMethod"]=="raw") ? value : JSON.stringify(value);
685
+
686
+ if ((_to(value) != "undefined") && (dopts["clear"] != "clear-only"))
687
+ _logdata.push([(new Date()).getTime()-_logdata[1][1], value, msg]);
688
+
689
+ return _logdata.length -1;
690
+ };
691
+ bw.log("","",{clear:"clear-only"}); // initialize
692
+
693
+ // =============================================================================================
694
+ bw.logd = function() {
695
+ /**
696
+ @method bw.logd()
697
+ @description: bw.logd is a log funciton which behaves similar to console.log() however instread of outputting to console,
698
+ it writes to bw.log() function with the following differences:
699
+
700
+
701
+ */
702
+ /*
703
+ todo: comma seperated items; ? done
704
+ console ==> also (attempt) to output to console.log ? would need to set a bw.state variable..
705
+ bwdbg ==> log bw catches / errors (else silent)
706
+ none ==> no output (of any kind)
707
+ stringify ==> takes bw.logd args and strinigyfies before writing to bw.log
708
+ example:
709
+ logd=console,bwlogd
710
+
711
+ */
712
+ var logdargs = ("bwlogd" in bw.bwargs) ? bw.bwargs["bwlogd"].split(",") : [];
713
+
714
+ if (logdargs.indexOf("none") < 0) {
715
+ var i=0;
716
+ var _a = [];
717
+ for (i=0; i< arguments.length; i++)
718
+ _a.push(arguments[i]); //arguments, a reserved javascript keyword, is not a true array
719
+ bw.log(_a,"bw.logd: "+bw.bwargs["bwlogd"]); // message
720
+ }
721
+ };
722
+ // =============================================================================================
723
+ bw.logExport = function(opts) {
724
+ /**
725
+ bw.logExport(options)
726
+ export the built in log.
727
+ default is "raw" which is an array of values:
728
+ [timestamp, <value logged>, <optional message from the event>]
729
+ [ .. , .. , .. ]
730
+
731
+ also can be exported as an HTML table.
732
+ bw.logExport({"format":"HTML"})
733
+
734
+ or as a simple text file:
735
+ bw.logExport("format" : "text"})
736
+
737
+ see bw.saveClientFile(fname) for saving the log as a file
738
+ */
739
+ var dopts = {
740
+ "format" : "raw" // can also be HTML table if set to "HTML"
741
+ };
742
+ dopts = optsCopy(dopts,opts);
743
+
744
+ var _ld = _logdata;
745
+
746
+ if (dopts["format"] == "HTML") {
747
+ return bw.makeHTMLTableStr(_ld,{sortable:true});
748
+ }
749
+
750
+ if (dopts["format"] == "text") {
751
+ return _ld.map(function(x){return x.map(function(y){return bw.padString(y.toString(),16,"left");}).join("\t");}).join("\n");
752
+ }
753
+
754
+ return _ld;
755
+ };
756
+
757
+
758
+ // ===================================================================================
759
+ bw.setCookie = function (cname, cvalue, exdays) {
760
+ /**
761
+ @method bw.setCookie(cookieName, value, expireDays) set a client side cookie. (browser only)
762
+ @param cname : a string for the name of the cookie
763
+ @param cvalue : a string for the value of the cookie
764
+ @param expdays : cookie expiration date in days
765
+ */
766
+ var d = new Date();
767
+ d.setTime(d.getTime() + (exdays*24*60*60*1000));
768
+ var expires = "expires="+d.toUTCString();
769
+ document.cookie = cname + "=" + cvalue + "; " + expires;
770
+ };
771
+
772
+ // ===================================================================================
773
+ bw.getCookie = function (cname, defaultValue) {
774
+ /**
775
+ @method bw.getCookie: bw.getCookie(cookieName, defaultValueIfNotFound) (browser only)
776
+ get a client side cookie, if it is set. returns defaultValue if cookie could not be found
777
+ */
778
+ var name = cname + "=";
779
+ var ca = document.cookie.split(";");
780
+ for(var i=0; i<ca.length; i++) {
781
+ var c = ca[i];
782
+ while (c.charAt(0)==" ") c = c.substring(1);
783
+ if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
784
+ }
785
+ return defaultValue;
786
+ };
787
+
788
+
789
+ // ===================================================================================
790
+ bw.getURLParam = function (key, defaultValue) {
791
+ /**
792
+ @method bw.getURLParam(key,defaultValueIfNotFound)
793
+ read the URL (e.g. http://example.com/my/page?this=that&foo=123&bub&x=123) and parse the URL paraemeters
794
+
795
+ x = bw.getURLParam() ==> returns entire dict of url params ==? {this:"that",foo:"123",bub:true,x:"123"}
796
+ x = bw.getURLParam("foo","whatever") ==> returns "123"
797
+ x = bw.getURLParam("bar","whatever") ==> returns "whatever" since bar isn't set
798
+ x = bw.getURLParam("bub","whatever") ==> returns true since bub doesn't have a value (note boolean true not "true")
799
+
800
+ */
801
+ if ((bw.isNodeJS()== true) || (typeof window != "object"))
802
+ return defaultValue;
803
+ try {
804
+ if (window.location.href) {
805
+ return bw.URLParamParse(window.location.href,key,defaultValue);
806
+ }
807
+ }
808
+ catch (e) {
809
+ bw.log(e);
810
+ }
811
+ return defaultValue;
812
+
813
+ };
814
+ bw.URLHash = function (url,defValue) {
815
+ /**
816
+ @method bw.URLHash(url,defValue) - returns the hash portion of a URL (if present) else return defValue
817
+ */
818
+ if (_to(url)=="undefined")
819
+ url = typeof window == "object" ? window.location.href : "";
820
+
821
+ var r = url.split(/#+/);
822
+ return url.includes("#") ? r[r.length-1] : defValue;
823
+ };
824
+ //=================================================
825
+ bw.URLParamParse = function (url,key,defValue,allowHash) {
826
+ /**
827
+
828
+ @method bw.URLParamParse(urlString, key, defaultValue)
829
+
830
+ decode a URL encoded string in to a javascript dictionary. Other params (http, port, path) are not handled
831
+
832
+ if key is present than only that value is returned (as a string ) else defValue is returned.
833
+
834
+ examples:
835
+ x = URLParamParse("http://example.com?a=123&b=345") ==> {a:"123", b:"456"}
836
+ x = URLParamParse("http://example.com?a=123&b=345","a") ==> "123"
837
+ x = URLParamParse("http://example.com?a=123&b=345","c","otherValue") ==> {a:"123", b:"456"} ==> "otherValue"
838
+ */
839
+
840
+ try {
841
+ var hs=function(u){var x = u.split(/^.*\?+/); return x.length==2 ? x[1] : "";};
842
+ var sh=function(u,b){return (b==true) ? u : u.split(/#+/)[0];};
843
+ var params={}, parts = sh(hs(url),allowHash).split("&");
844
+ for (var i = 0; i < parts.length; i++) {
845
+ var e = parts[i].split("=");
846
+ if (!e[0])
847
+ continue;
848
+ params[decodeURIComponent(e[0])] = _to(e[1])=="string" ? decodeURIComponent(e[1].replace("#","%23")) : true;
849
+ }
850
+ if (_to(key)=="undefined")
851
+ return params;
852
+ return params.hasOwnProperty(key) ? params[key] : defValue;
853
+ }
854
+ catch (e) {
855
+ bw.log(e);
856
+ return defValue;
857
+ }
858
+ };
859
+ //=================================================
860
+ bw.URLParamPack = function (simpleDict,inclQuestion) {
861
+ /**
862
+ @method bw.URLParamPack(simpleDict, inclQuestion) : packs a simple dict in to URL encoded format
863
+ @param simpleDict(object) - dictionary of simple key value pairs (not nested) if "deep" JSON needs to be packed then stringify that first
864
+ @param InclQuestion(boolean) - if true adds "?" to string otherwise ommitted.
865
+
866
+ see also URLParamParse.
867
+ note if using bw.URLParamParse besure to include "?"" ==> bw.URLParamParse(bw.URLParamPack({a:1,b;2},"true"))
868
+ */
869
+ var k,s=[];
870
+ if (_to(simpleDict) == "object") {
871
+ for (k in simpleDict) {
872
+ s.push([encodeURIComponent(k)+"="+encodeURIComponent(simpleDict[k].toString())]);
873
+ }
874
+ s = s.join("&");
875
+ }
876
+ else
877
+ s="";
878
+
879
+ return (inclQuestion ? "?" : "") + s;
880
+ };
881
+
882
+
883
+
884
+ // ===================================================================================
885
+ bw.htmlSafeStr = function (str) {
886
+ /**
887
+ bw.htmlSageString(str)
888
+ Replace non valid HTML characters with HTML escaped equivalents.
889
+ */
890
+ //generic way..
891
+ //var x = function(x){return "&#"+x.toString().charCodeAt(0)+";";}
892
+ //return (str.toString()).replace(/[<>&\\#]/gm,x).replace(/[\n]/gm,"<br>");
893
+
894
+ //old way is "pretty", tabs are issued 4 spaces..
895
+ var c = {"<":"&lt;", ">":"&gt;", "&":"&amp;", "\"":"&quot;", "'":"&#039;","#":"&#035;","\\\\":"","\n":"<br>","\t":"&nbsp;&nbsp;&nbsp;&nbsp;"};
896
+ return (str.toString()).replace(new RegExp("["+Object.keys(c).join("")+"]","gm"),function(s){return c[s];});
897
+ };
898
+
899
+ // ===================================================================================
900
+ bw.htmlFavicon = function(iconStr,color) {
901
+ /**
902
+ create and HTML favicon from a string. Can use any unicode char including emoticons.
903
+ */
904
+ iconStr = iconStr ? iconStr : "🔧";// ♪ ❤ ♡ 🦉 🔧 🌴 ♫
905
+ var c = bw.to(color)=="string" ? color : "black";
906
+ return bw.html( {t:"link", a: {href: "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='0.9em' font-size='90' style='fill:"+ c+"'>"+iconStr+"</text></svg>", rel: "icon"} } );
907
+ };
908
+
909
+ // ===================================================================================
910
+ bw.htmlJSON=function (json,pwrap) {
911
+ /**
912
+ @method bw.htmlJSON(object, styles)
913
+ pretty print any javascript object as displayable HTML.
914
+ e.g.
915
+ document.getElementById("myPlaceToDisplay").innerHTML = bw.htmlJSON(...any object ....)
916
+ */
917
+ //TODO make style dict as a param
918
+ function f(json) {
919
+ json = JSON.stringify(json, undefined, 2);
920
+ if (typeof json != "string") { json = JSON.stringify(json, undefined, 2);}
921
+
922
+ json = json.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); //html safe chars
923
+ //json = bw.htmlSafeStr(json);
924
+ return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g, function (match) {
925
+ var sty = "color: darkorange;";
926
+ if (/^"/.test(match)) {
927
+ if (/:$/.test(match)) {
928
+ sty = "color:red";
929
+ } else {
930
+ sty = "color:purple";
931
+ }
932
+ } else if (/true|false/.test(match)) {
933
+ sty = "color:grey";
934
+ } else if (/null/.test(match)) {
935
+ sty = "color:black";
936
+ } else
937
+ sty = "color:green";
938
+ return "<span style=\"" + sty + "\">" + match + "</span>";
939
+ });
940
+ }
941
+ pwrap = _toa(pwrap,"undefined","white-space:pre-wrap;","");
942
+
943
+ return "<pre style='"+pwrap+"'>"+f(json)+"</pre>";
944
+ };
945
+
946
+ // ===================================================================================
947
+ bw.makeCSS = function (cssData, options) {
948
+ /**
949
+ bw.makeCSS(cssData, options)
950
+
951
+ cssData = "h2 {color:blue;}" // string as full rule (all correctness is on the caller)
952
+ cssData = ["h2 {color:blue;}"] // array entry but single string
953
+ cssData = ["h2 {color:blue}", "div {width:30px}"] // 2 entries, both strings
954
+ cssData = [["h2","color:blue"]] // array of rules (here length 1 rule)
955
+ cssData = [["h2","color:blue"], ["h3", "font-color:red"]] // array of rules
956
+ cssData = [
957
+ [["h2","h4"], "color:blue"], // array or selectors, string for rule
958
+ ["h3", "color:red"] // string for selector, string for rule
959
+ ]
960
+ cssData = [
961
+ [["h1","div p"],["color:blue","display:block"]], ==> h1, div p {color: blue; diplay:block;}
962
+ "h3 {color:red;}", ==> h3 {color: red;}
963
+ [["div",".myClass"],"color : red"], ==> div,.myClass {color: red;}
964
+ ["p > .myclass", ["color:red","display:block"]] ==> p > .myClass {color: red; display:block;}
965
+ ]
966
+ cssData = [
967
+ [str, {}]
968
+ ]
969
+ cssData = [
970
+ [[selectors], { dict }]
971
+ ]
972
+
973
+ dicts not used at root because css can have multiple redundant selectors with different rules
974
+
975
+ */
976
+ var dopts = {
977
+ emitStyleTag: false,
978
+ atr: {},
979
+ pretty : false
980
+ };
981
+ dopts = optsCopy(dopts,options);
982
+
983
+ var s="\n";
984
+ var tb = function (a) {a =(String(a)).trim(); a=(a[0]=="{"?" ":" {")+a; a+=(a[a.length]=="}"?"":"}")+"\n"; return a;};
985
+ //var rl = "";
986
+ try {
987
+ switch (_to(cssData)) {
988
+ case "string":
989
+ s += cssData +"\n";
990
+ break;
991
+ case "array":
992
+ var i;
993
+ for (i=0; i<cssData.length; i++) {
994
+ var j = cssData[i];
995
+ switch (_to(j)) {
996
+ case "string": // this means we assume correcly formatted style is being passed in and we're just letting it through e.g. ".myclass {color:red}"
997
+ s+= j+"\n";
998
+ break;
999
+ case "array" : //expects length 2 array for each entry, though 2nd member can be dict or array
1000
+ // ==>[str, str], [[str,str,str],str] , [str, {}], [[str,str,str],{}]
1001
+ if ((j.length == 1) && (_to(j[0])=="string")) {
1002
+ s+= j[0]+"\n";
1003
+ break;
1004
+ }
1005
+
1006
+ if (j.length == 2) {
1007
+ var _name = j[0], _rule = j[1], _ruleOutput="";
1008
+ if (_to(_name)=="array") {
1009
+ s+= _name.join(", ");
1010
+ }
1011
+ else {
1012
+ s+= String(_name);
1013
+ }
1014
+ // now we have the names e.g. ("h2" or "h2,.myClass") done we need to emit the rules
1015
+ switch( _to(_rule)) {
1016
+ case "array" : // ["h2", ["color: black","left:20%"]] or [["h2",".myClass"], ["color: black","left:20%"]]
1017
+ _ruleOutput = _rule.join("; ")+";";
1018
+ break;
1019
+ case "object" : // ["h2", {color: "black", left:"20%"}] or [["h2",".myClass"], {color:black, left:"20%"}]
1020
+ {
1021
+ var x;
1022
+ for (x in _rule) { _ruleOutput += (x + ": " + _rule[x]+"; ");}
1023
+ //_ruleOutput = bw.makeCSSRule([_name,_rule],{pretty:opts.pretty});
1024
+ }
1025
+ break;
1026
+ case "string": // ["h2", "color: black"] or [["h2",".myClass"], "color:black"]
1027
+ default:
1028
+ _ruleOutput=_rule;
1029
+ }
1030
+ s+= tb(_ruleOutput)+"\n";
1031
+
1032
+ }
1033
+
1034
+ break;
1035
+ default:
1036
+ }
1037
+ }
1038
+
1039
+ break;
1040
+ default:
1041
+ s="";
1042
+ }
1043
+ }
1044
+ catch (e) {bw.logd(e);} // eslint-disable-line no-empty
1045
+ if (dopts["emitStyleTag"]) {
1046
+ s = bw.html(["style",dopts["atr"],s]);
1047
+ }
1048
+ s.replace(/\n+/g,"\n").replace(/s+/g," ");
1049
+ return s;
1050
+ };
1051
+
1052
+ // ===================================================================================
1053
+ bw.makeCSSRule = function (cssData, options) {
1054
+ /**
1055
+ @method bw.makeCSSRule(cssData, options)
1056
+
1057
+ expects this form:
1058
+ [str, {k,v}]
1059
+ or
1060
+ [[array of rules str], {k,v}]
1061
+
1062
+ e,g, [".myClass", {"color": "red", "font-weight" : "700 !important!"}]
1063
+ or
1064
+ [[".myClass","div > p"], {"color": "red", "font-weight" : "700 !important!"}]
1065
+
1066
+ */
1067
+ var dopts = {
1068
+ emitStyleTag: false,
1069
+ atr: {},
1070
+ pretty: true // make it pretty otherwise compact form generated
1071
+ };
1072
+ dopts = optsCopy(dopts,options);
1073
+
1074
+ var k,d,v=[],s="",sp=dopts.pretty?" ":"", cr=dopts.pretty?"\n":"";
1075
+
1076
+ try {
1077
+ if (_to(cssData)== "array") {
1078
+ k=cssData[0], d=cssData[1];
1079
+ s +=_toa(k,"array",k,[k.toString()]).join(","+sp)+cr;
1080
+ for (k in d) {
1081
+ v.push([sp+sp+k+":"+sp+cssData[1][k]+";"+sp+cr]);
1082
+ }
1083
+ s+= "{"+cr+v.join("")+"}"+cr;
1084
+ }
1085
+ } catch(e) {
1086
+ bw.logd(e);
1087
+ }
1088
+ return s;
1089
+
1090
+ };
1091
+
1092
+ // ===================================================================================
1093
+ /*
1094
+ bw.htmlPage = function (head, body, options) {
1095
+ /**
1096
+ TBD finish (include bw params, handling meta w/o close tags)
1097
+ bw.makeHTMLDoc(head,body,options)
1098
+ make a simple HTML document.
1099
+
1100
+ inline-bw-css --> emit bw default styles as inline css (include globals option)
1101
+ * /
1102
+ var dopts = {
1103
+ docType : "<!DOCTYPE html>",
1104
+ htmlParams : {lang: "en"},
1105
+ headDefaultContent : [
1106
+ ["meta", {"http-equiv":"Content-Type", "content":"text/html", "charset":"utf-8"}, ""]
1107
+ //["title", {}, "bw doc"]
1108
+ ],
1109
+ headIncludeBitWrenchJS : false, // false : don't include, "embed" or "path-string"
1110
+ headIncludeBitWrenchCSS : false, // exports bitwrench css classes in <style> section in head
1111
+ headFavicon : "" //<link rel="icon" type="image/x-icon" href="../images/favicon-32x32.png" />
1112
+ };
1113
+ dopts = optsCopy(dopts,options);
1114
+
1115
+ var s = dopts["docType"]+"\n";
1116
+ s += bw.html(["html",dopts["htmlParams"],[
1117
+ "\n",
1118
+ ["head", {}, [ "\n",dopts["headDefaultContent"].map(function(x){return bw.html(x);}).join("\n"),head,"\n"]],
1119
+ "\n",
1120
+ ["body", {}, [ "\n",body,"\n"]],
1121
+ "\n"
1122
+ ]]);
1123
+ return s;
1124
+ };
1125
+ */
1126
+ // ===================================================================================
1127
+ /**
1128
+ htmlIsVoidTag(tagString) returns true if the supplied string is a html void tag (e.g. meta or br) which doesn't require a closing bracket else false
1129
+ */
1130
+ bw.htmlIsVoidTag = function(tag) {
1131
+ return " area base br col command embed hr img input keygen link meta param source track wbr ".search(" "+String(tag).trim().toLowerCase()+" ") >=0;
1132
+ };
1133
+ // ===================================================================================
1134
+ bw.htmlNode = function(x,opts) {
1135
+ /**
1136
+
1137
+ bw.htmlNode - converts acceptable data contructs into htmlEmit() compatible form,
1138
+ { t: <tag>, a: {attribs}, c: [content], o: {options} }
1139
+ or
1140
+ a string e.g. "my html content" note that this can include html as well.
1141
+ or
1142
+ a function to be evaluated later
1143
+ f(){return htmlNode compatible data structure} ==> when bw.HTMLEmit encounters this it will evaluate the the function contents. Useful for live templating.
1144
+
1145
+ How htmlNode handles different objects:
1146
+
1147
+ "object"
1148
+ accepted keys below, other keys ignored (e.g. if your objects looks like this: {tag:"div", c:["this is my content"], data: [ ....] } the key called data will be ignored by htmlNode
1149
+ t: String | Number | Date() ==> tag function==> f().toString()
1150
+ a: {} ==> key : value ==> num | str | Date | [] ==> [].join(dopts.a_join) ,
1151
+ c: [] || String | Number | Date ==> each_item : str | {html_dict}
1152
+ o: {} ==> options (note inherit / copy) => if not supplied uses previous levels options
1153
+
1154
+
1155
+ also accepts: "tag", "attrib", "content", "options" as keys instead of t,a,c,o,s
1156
+
1157
+ if any of t,a,c,o are a function it will be invoked immediatly w no params ==> t:myFunc ===> t:myFunc() <==
1158
+
1159
+ defaults:
1160
+ t ==> "div"
1161
+ a ==> {}
1162
+ c ==> []
1163
+ o ==> {}
1164
+
1165
+ s ==> {level:0, nodes: 0}
1166
+
1167
+ "string" | "number" | Date() ==> {}
1168
+ t ==> "div"
1169
+ a ==> {}
1170
+ c ==> .toString()
1171
+ o ==> {}
1172
+
1173
+ s ==> {}
1174
+
1175
+ "array" type objects are mapped BW_HTMLNodes as follows:
1176
+ [ ] ==> {t:"span", a: {}, c: "", o: {}}
1177
+ [c ] ==> {t:"span", a: {}, c: c , o: {}}
1178
+ [t,c ] ==> {t:t, a: {}, c: c , o: {}}
1179
+ [t,a,c ] ==> {t:t, a: a, c: c}
1180
+ [t,a,c,o ] ==> {}
1181
+ [ 5+ ] ==> {} // uses, first 4 entries, others ignored
1182
+
1183
+ // this dict repreesnts the mapping
1184
+ {
1185
+ 0 : { }
1186
+ 1 : {c : 0},
1187
+ 2 : {t : 0, c : 1},
1188
+ 3 : {t : 0, a : 1, c : 2}
1189
+ 4 : {t : 0, a : 1, c : 3, o : 4}
1190
+ 5 : {t : 0, a : 1, c : 3, o : 4, s : 5}
1191
+ }
1192
+
1193
+ // this array contruct implements the above dict mapping more compactly
1194
+ var i,idx = [[],["c"], ["t","c"], ["t","a","c"],["t","a","c","o"],["t","a","c","o","s"]];
1195
+ for (i=0; i< x.length; i++)
1196
+ hd[idx[x.length]][i] = x[i];
1197
+
1198
+ returns [BWHTMLNode object,errorInfoString]
1199
+
1200
+ */
1201
+
1202
+ var err="",dopts = {
1203
+ functionExec : true, // if this node data is a function, execute it with no params eg x ====> x()
1204
+ atomic2span : false // convert atomic strings to a span element
1205
+ };
1206
+
1207
+ dopts = optsCopy(dopts,opts);
1208
+ var isv = bw.htmlIsVoidTag;
1209
+ var isnu = function(x) {return bw.toa(x,["null","undefined"],true,false);}; // is x null or undefined
1210
+ var HTMLNode = function BW_HTMLNode() {this.t="div"; this.a={}; this.c=[]; this.o={tagClose:"auto"}; this._is_BW_HTMLNode=true;}; //isBWHTMLNode is for IE compatiblity
1211
+ //function bwError (v,x) {this.value=v; this.msg = (typeof x == "undefined") ? "error" : x;}
1212
+
1213
+ var i,n = new HTMLNode(); // default html dict format
1214
+ switch (bw.to(x)) {
1215
+ case "null" :
1216
+ case "undefined" :
1217
+ n = "";
1218
+ err = "error: html node content is " + bw.to(x);
1219
+ break;
1220
+ case "object":
1221
+ [["tag","t"],["attrib","a"],["content","c"],["options","o"]].forEach(function(z){ n[z[1]]= z[0] in x ? x[z[0]] : n[z[1]];});
1222
+ for (i in n) { // we only copy those fields we care about..
1223
+ n[i] = (i in x) ? x[i] : n[i]; // need to handle complicated types: t:"", a:{}, c:"" | []
1224
+ if (isnu(n[i])) {
1225
+ n = ""; // force entire object to be null or undefined
1226
+ err = ("Error HTMLNode : a field is null or undefined");
1227
+ break;
1228
+ }
1229
+ }
1230
+ break;
1231
+ case "BW_HTMLNode" :
1232
+ for ( i in x) { n[i] = x[i];}
1233
+ break;
1234
+ case "array":
1235
+ var idx = [[],["c"], ["t","c"], ["t","a","c"],["t","a","c","o"]];
1236
+ var m = (x.length > 4) ? 4 : x.length;
1237
+ for (i=0; i< m; i++) {
1238
+ // console.log(idx[m][i] + ":" + x[i]);
1239
+ n[idx[m][i]] = x[i];
1240
+ }
1241
+ for (i in n)
1242
+ if (isnu(n[i])) {
1243
+ n = "";
1244
+ err = "Error HTMLNode : bad array array input";
1245
+ break;
1246
+ }
1247
+ //n.c = _toa(n.c,"array",n.c,[n.c]);
1248
+ break;
1249
+ case "function": // this whole node is a function
1250
+ var h;
1251
+ if (dopts.functionExec) {
1252
+ h = bw.htmlNode(x(),dopts);
1253
+ n=h.node;
1254
+ err = h.error;
1255
+ }
1256
+ else
1257
+ n=h;
1258
+ break;
1259
+ default: // string, number, Date, bool, Regex ==> will be come just plain rendered content later
1260
+ if (dopts.atomic2span) {
1261
+ n.c =[x.toString()];
1262
+ n.t = "span";
1263
+ }
1264
+ else
1265
+ n = x.toString();
1266
+ }
1267
+ var r= {
1268
+ node : n,
1269
+ ntype : bw.typeOf(n), // BW_HTMLNode | string | function
1270
+ error : err,
1271
+ isVoidTag : (bw.typeOf(n)=="BW_HTMLNode")? isv(n.t) : false
1272
+ };
1273
+
1274
+ return r;
1275
+ };
1276
+ // ===================================================================================
1277
+ bw.htmlEmit = function(htmlData, opts, state) {
1278
+
1279
+ var dopts = {
1280
+ tagClose : "inherit",
1281
+ htmlEscContent : false // change spaces, /n /t to html equivalents
1282
+ };
1283
+
1284
+ state = bw.toa(state,["undefined","null"], {
1285
+ nodesCnt : 0,
1286
+ levelCnt : 0,
1287
+ levelMax : 0
1288
+ },state);
1289
+
1290
+ dopts = optsCopy(dopts,opts);
1291
+
1292
+ var _atr = function(n){
1293
+ // handle "smart" attributes ==>
1294
+ // class : ["class1", "class2"] ==> style : bw.makeCSS(),
1295
+ // functions e.g. onclick:function(){} // html on... events can be encoded auto e.g. onclick=function() ==> onclick=bw.registerfunction(... )
1296
+
1297
+ var as = [], k,v,vr;
1298
+
1299
+ for (k in n.a) {
1300
+ v = n.a[k]; // now we have k, v
1301
+ if (v == null) {
1302
+ as.push(k); //null values are ignored. e.g.g {t:"div", a:{checked:null, c:...} ==> <div checked /> ...
1303
+ continue;
1304
+ }
1305
+ if (k.search(/^on/) >=0 ) { // its a on... hanlder
1306
+ if (bw.to(v) == "function") {
1307
+ if (n.o["atrOnEventRegister"] == false) {
1308
+ vr = String(v()); // its a function but execute it and return a string value
1309
+ }
1310
+ else { // register it
1311
+ vr = bw.funcGetDispatchStr(bw.funcRegister(v),"this"); //
1312
+ }
1313
+ }else { // not a function but some other type
1314
+ vr = "";
1315
+ try {
1316
+ vr = v.toString();
1317
+ } catch (e) { vr = String(v);}
1318
+ }
1319
+ }
1320
+ else // not an "on" handler
1321
+ {
1322
+
1323
+ switch(k) {
1324
+ case "style" :
1325
+ if (_to(v) == "string")
1326
+ vr=v.toString();
1327
+ else
1328
+ vr = bw.makeCSSRule(["",v],{pretty:false}).trim().replace(/^{/,"").replace(/}$/,"").trim();
1329
+ break;
1330
+ default :
1331
+ if (bw.to(v)=="array")
1332
+ vr = v.join(" ");
1333
+ vr = v.toString();
1334
+ }
1335
+ }
1336
+ as.push(k+"="+"\""+vr.replace("\"","\\\"")+"\"");
1337
+ }
1338
+ as = as.join(" ");
1339
+ return (as.length > 0 ? " ": "") + as + (as.length > 0 ? " ": "");
1340
+ };
1341
+
1342
+ var h=[],n= bw.htmlNode(htmlData);
1343
+
1344
+ if (_to(n.node) == "function") {
1345
+ n = bw.htmlNode(n.node());
1346
+ n = _toa(n.ntype,["BW_HTMLNode","string"],n,""); // if its still not a string or BW_HTMLNode we just need to punt it.
1347
+
1348
+ }
1349
+ state.nodesCnt++;
1350
+ if (n.ntype != "BW_HTMLNode") {
1351
+ h.push( dopts.htmlEscContent ? bw.htmlSafeStr(n.node.toString()) : n.node.toString() );
1352
+ }
1353
+ else { // bw_HTMLNode
1354
+
1355
+ h.push("<",n.node.t, _atr(n.node));
1356
+
1357
+ var tagClose = dopts.tagClose != "inherit" ? dopts.tagClose : n.node.o.tagClose;
1358
+ switch(tagClose) {
1359
+ /*eslint no-fallthrough: [0, { "commentPattern": "break[\\s\\w]*omitted" }]*/
1360
+ case "auto":
1361
+ if (n.isVoidTag){
1362
+ // <tag a{} /> # content is not rendered for void tags # ["<",n.t , a{}, "/>" ]
1363
+ h.push( "/>");
1364
+ break;
1365
+ }
1366
+
1367
+ case "all":
1368
+ default:
1369
+ //<tag a{}> .... </tag> # h=["<",n.t , a{}, crend() , "</", n.t, ">"]
1370
+ h.push(">");
1371
+ var i,x;
1372
+ if (bw.typeOf (n.node.c) != "array") {
1373
+ state.levelCnt++;
1374
+ state.levelMax = state.levelCnt > state.levelMax ? state.levelCnt : state.levelMax;
1375
+ x = bw.htmlEmit(n.node.c,dopts,state);
1376
+ state.levelCnt--;
1377
+ h.push(x.html);
1378
+ }
1379
+ else {
1380
+ for (i in n.node.c) {
1381
+ state.levelCnt++;
1382
+ state.levelMax = state.levelCnt > state.levelMax ? state.levelCnt : state.levelMax;
1383
+ x= bw.htmlEmit(n.node.c[i],dopts,state);
1384
+ state.levelCnt--;
1385
+ h.push(x.html);
1386
+ }
1387
+ }
1388
+ if ( tagClose != "none" )
1389
+ h.push( "</",n.node.t,">");
1390
+ }
1391
+
1392
+ }
1393
+ var html = h.join("");
1394
+ return {html: html, state: state};
1395
+ };
1396
+ // ===================================================================================
1397
+ bw.html = function(data,options) {
1398
+ return bw.htmlEmit(data,options).html;
1399
+ };
1400
+ // ===================================================================================
1401
+ bw.htmla = function (listData,options) {
1402
+ /**
1403
+ bw.htmla(listData,options)
1404
+
1405
+ listData is a single dim array of bw.html() compatible cnostructs
1406
+
1407
+ */
1408
+ if (_to(listData) != "array")
1409
+ return bw.html(listData,options);
1410
+
1411
+ return listData.map( function(x) {return bw.html(x,options);}).join("");
1412
+ };
1413
+
1414
+ // ===================================================================================
1415
+ bw.htmlList = function (listData, listType, atr, atri) {
1416
+ /**
1417
+ bw.makeHTMLList (listData, listType, attribute{}, attribute_for_each_items {})
1418
+
1419
+ listType = "ul" | "ol"
1420
+ listData = [ item1, item2, item3, .. ]
1421
+
1422
+ */
1423
+ if (_to(listData) != "array")
1424
+ return "";
1425
+
1426
+ if (listData.length < 1)
1427
+ return "";
1428
+
1429
+ atr = _toa(atr,"object",atr,{});
1430
+ atri = _toa(atr,"object",atr,{});
1431
+
1432
+ var lc = listData.map(function(x){return bw.html(["li",atri,x]);});
1433
+ listType = ["ul","ol"].indexOf(listType)== -1 ? "ol" : listType;
1434
+ return bw.html ({t:listType,a:atr,c:lc});
1435
+ };
1436
+
1437
+ // ===================================================================================
1438
+ bw.openFullScreen = function () {
1439
+ /**
1440
+ bw.openFullScreen() attempt to open the document full screen (usefull for signs, banners)
1441
+ */
1442
+ var elem = document.documentElement;
1443
+ if (elem.requestFullscreen) {
1444
+ elem.requestFullscreen();
1445
+ } else if (elem.mozRequestFullScreen) { /* Firefox */
1446
+ elem.mozRequestFullScreen();
1447
+ } else if (elem.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
1448
+ elem.webkitRequestFullscreen();
1449
+ } else if (elem.msRequestFullscreen) { /* IE/Edge */
1450
+ elem.msRequestFullscreen();
1451
+ }
1452
+ };
1453
+
1454
+ // ===================================================================================
1455
+ bw.classStrAddDel = function (classData,classesToAdd,classesToDel) {
1456
+ /**
1457
+ classStrAddDel (classData, classesToAdd, classesToDel)
1458
+ for CSS classes
1459
+
1460
+ takes a valid classData string e.g. "myclass1 myclass2" etc
1461
+
1462
+ and adds/del classes from classesToAdd string if they are not already present in classData
1463
+
1464
+ classStrAddDel("class1 class2", "class3") ==> "class1 class2 class3"
1465
+ classStrAddDel("class1 class2", "class3 class4") ==> "class1 class2 class3 class4"
1466
+ classStrAddDel("class1 class2", "class2 class3") ==> "class1 class2 class3" // doesn't add class2 again
1467
+
1468
+ classStrAddDel("class1 class2", "class 2 class3",class1) ==> "class2 class3" // doesn't add class2 again. removes class1
1469
+ classStrAddDel("class1 class2", "",class1) ==> "class2" // removes class1
1470
+
1471
+ classData, classesToAdd, classesToDel may be strings (space delimited) or arrays of strings (["c1", "c2"], ["c3", "c4"], ["c1"])
1472
+ */
1473
+
1474
+ var tnorm = function(x){x=bw.toa(x,"undefined",[],x); return (bw.to(x)=="array")? x : x.toString().trim().split(/\s+/ig);};
1475
+ var c = tnorm(classData);
1476
+ var ca = tnorm(classesToAdd);
1477
+ var cd = tnorm(classesToDel);
1478
+ return bw.arrayBNotInA(cd,c.concat(ca)).join(" ").trim().replace(/\s+/ig," ");
1479
+
1480
+ };
1481
+ // ===================================================================================
1482
+ bw.classStrToggle = function (classData, classesToToggle) {
1483
+ /**
1484
+ classStrToggle (classData, classesToToggle)
1485
+
1486
+ toggles classes listed in classesToToggle
1487
+
1488
+ takes a valid classData string e.g. "myclass1 myclass2" etc
1489
+ */
1490
+ var tnorma = function(x){x=bw.toa(x,"undefined",[],x); return (bw.to(x)=="array")? x : x.toString().trim().split(/\s+/ig);};
1491
+ var c = tnorma(classData);
1492
+ var t = tnorma(classesToToggle);
1493
+ return bw.classStrAddDel(classData,bw.arrayBNotInA(c,t),bw.arrayBinA(c,t));
1494
+ };
1495
+
1496
+ // ===================================================================================
1497
+ bw.htmlTabs = function(tabData, opts) {
1498
+ /**
1499
+ bw.makeHTMLTabs(tabData, atr)
1500
+ tabData = [[tab1Title,tab1-content], [tab2Title,tab2-content], [tab3Title,tab3-content]]
1501
+ */
1502
+ if (_to(tabData) != "array")
1503
+ return "";
1504
+ if (tabData.length < 1)
1505
+ return "";
1506
+
1507
+ var dopts = {
1508
+ atr : {"class":""}, //container {}
1509
+ tab_atr : {"class":""}, //attributs for each tab container
1510
+ tabc_atr: {"class":""}, //attributes for each tab-content area container
1511
+ indent : "", //indent string for pretty printing
1512
+ pretty : false
1513
+ };
1514
+ dopts = optsCopy(dopts,opts);
1515
+
1516
+ var ti = tabData.map(function(x){return ["li",{"class":"bw-tab-item", "onclick":"bw.selectTabContent(this)"},x[0]];});
1517
+ var tc = tabData.map(function(x){return ["div",{"class":"bw-tab-content"},x[1]];});
1518
+
1519
+ ti[0][1]["class"] = bw.classStrAddDel(ti[0][1]["class"], "bw-tab-active");
1520
+ tc[0][1]["class"] = bw.classStrAddDel(tc[0][1]["class"], "bw-show");
1521
+
1522
+ dopts["atr" ]["class"] = bw.classStrAddDel (dopts["atr" ]["class"],"bw-tab-container");
1523
+ dopts["tab_atr" ]["class"] = bw.classStrAddDel (dopts["tab_atr" ]["class"],"bw-tab-item-list");
1524
+ dopts["tabc_atr"]["class"] = bw.classStrAddDel (dopts["tabc_atr"]["class"],"bw-tab-content-list");
1525
+
1526
+ return bw.html({t:"div",a: dopts["atr"],c:[["ul",dopts["tab_atr"],ti],["div",dopts["tabc_atr"],tc]]});
1527
+ };
1528
+
1529
+
1530
+ // ===================================================================================
1531
+
1532
+ bw.htmlTable = function(data,opts) {
1533
+ /**
1534
+ bw.makeHTMLTableStr (data, options)
1535
+
1536
+ Creates an HTML table element (as a string) from raw array data.
1537
+
1538
+ var table1 =
1539
+ [["this", "that", "the", "other"],[,6,4,0,4],[3,5,1,4],[1,2,4,5],["2u30","23",function(){return 834},23]];
1540
+ document.getElementById("myTableDiv") = bw.makeHTMLTableStr(table1); // displays simple table.
1541
+
1542
+ var options = {
1543
+ useFirstRowAsHeaders:false, // first row is data
1544
+ caption:"Important Table" // caption
1545
+ sortable: false | true | function // make table sortable (false is default, if true uses bw built-in sort, else supply function)
1546
+ }
1547
+ document.getElementById("myTableDiv") = bw.htmlTable(table1, options);
1548
+
1549
+ Options:
1550
+ useFirstRowAsHeaders : true; //
1551
+ */
1552
+ if ((_to(data) != "array") || (data.length < 1))
1553
+ return "";
1554
+
1555
+ //default options
1556
+ var dopts = {
1557
+ useFirstRowAsHeaders : true,
1558
+ useDefaultStyle : true, // for
1559
+ atr : {}, // attributes for table object can use function() for dynamic
1560
+ thead_atr : {}, // attributes for table head section
1561
+ th_atr : {}, // attributes for header cells,
1562
+ tbody_atr : {}, // atttributs for table body section
1563
+ tr_atr : {}, // attributes for rows
1564
+ td_atr : {}, // attributes for cells
1565
+ caption : "", // optional table caption (can be HTML, or function, bw.buildHTMLObjString compatible data)
1566
+ sortable : false// make table sortable. if true, uses default sort, otherwise pass function to sort table. f(a,b,optionalColumnNumber)
1567
+ };
1568
+
1569
+ var i=0,head="",body="",r,_hs=bw.html;
1570
+ dopts = optsCopy(dopts,opts);
1571
+
1572
+ if (dopts.useDefaultStyle) {
1573
+ // dopts.atr["class"] = "bw-table-stripe bw-table-col0-bold bw-table-compact bw-table-border-round bw-table-head bw-table-cellpad";
1574
+ dopts.atr["class"] = "bw-table bw-table-stripe";
1575
+ }
1576
+ if (dopts.sortable == true) {
1577
+ dopts.th_atr["onclick"] = "bw.sortTableDispatch(this)";
1578
+ if ("class" in dopts.th_atr)
1579
+ dopts.th_atr["class"] += dopts.th_atr["class"].split(/[ ]+/).indexOf("bw-table-sort-xxa") <0 ? " bw-table-sort-xxa" : "";
1580
+ else
1581
+ dopts.th_atr["class"] = "bw-table-sort-xxa";
1582
+ }
1583
+ else {
1584
+ if (_to(dopts.sortable) == "function") {
1585
+ var sfid = bw.funcRegister(dopts.sortable);
1586
+ dopts.th_atr["onclick"] = bw.funcGetDispatchStr(sfid,"this");
1587
+ }
1588
+ }
1589
+
1590
+ if (dopts["useFirstRowAsHeaders"]) {
1591
+ head=data[0].map(function(x){return _hs({t:"th",a:dopts.th_atr,c:x});}).join("");
1592
+ head= _hs({t:"tr",a:dopts.tr_atr,c:head});
1593
+ i=1;
1594
+ }
1595
+ else
1596
+ i=0;
1597
+ head = bw.html({t:"thead",a:dopts.thead_atr,c:head});
1598
+
1599
+ for (; i<data.length; i++) {
1600
+ r = data[i].map(function(x){return _hs({t:"td",a:dopts.td_atr,c:x});}).join("");
1601
+ body+= _hs({t:"tr",a:dopts.tr_atr,c:r});
1602
+ }
1603
+ body = bw.html({t:"tbody",a:dopts.tbody_atr,c:body});
1604
+ //console.log(head,'\n',body);
1605
+ dopts.caption = dopts.caption == "" ? "" : _hs({t:"caption",a:{},c:dopts.caption});
1606
+ return _hs({t:"table",a:dopts.atr,c:[dopts.caption,head,body]});
1607
+ };
1608
+
1609
+
1610
+ bw.htmlAccordian = function (data, opts) {
1611
+ /**
1612
+ htmlAccordian
1613
+
1614
+ [[data-title, data-to-show, {show: true|false}], // show determines whether the default content is visible
1615
+ [...]]
1616
+
1617
+ data-title and data-to-show can be strings or any valid bw.html() constructs
1618
+ */
1619
+ var s = "";
1620
+ if (_to(data) !== "array")
1621
+ return s;
1622
+
1623
+ var dopts = {
1624
+ "atr" : { "class": "bw-accordian-container"}, // div for overall accordian
1625
+ "atr_h" : { "onclick":"bw.DOMClassToggle(this.nextSibling,'bw-hide')", "class" : "bw-thm-light"}, // div wrapping each header
1626
+ "atr_c" : { "class":"bw-hide"} // div wrapping each content
1627
+ };
1628
+ dopts = optsCopy(dopts,opts);
1629
+ dopts.atr_h["onclick"]="bw.DOMClassToggle(this.nextSibling,'bw-hide')";
1630
+ //dopts.atr_h["class"]="bw-thm-light";
1631
+ //console.log(dopts);
1632
+ s = data.map(function(x){
1633
+ var a=dopts["atr_c"],show;
1634
+ show = ( (x.length > 2) && (x[2].show==true));
1635
+
1636
+
1637
+ if (a["class"]) {
1638
+ a["class"] = show ? bw.classStrAddDel(a["class"],"","bw-hide"):bw.classStrAddDel(a["class"],"bw-hide") ;
1639
+ }
1640
+ else a["class"]=show ? "":"bw-hide";
1641
+
1642
+ return bw.html({t:"div",a:dopts["atr_h"],c:[x[0]]})+bw.html({t:"div",a:a,c:[x[1]]} );
1643
+ }).join("");
1644
+ s = bw.html({t:"div",a:dopts["atr"],c:[s]});
1645
+ return s;
1646
+ };
1647
+ // ===================================================================================
1648
+ bw.htmlSign = function (content, opts) {
1649
+ /**
1650
+ htmlSign("my content",options)
1651
+ create a centered banner / billboard
1652
+ */
1653
+ var dopts = {
1654
+ atr : {style:{"font-weight":"700", "font-size":"7em"}},
1655
+ escContent : false,
1656
+ };
1657
+
1658
+ dopts = optsCopy(dopts,opts);
1659
+ content = dopts.escContent!=false ? bw.htmlSafeStr(content) : content;
1660
+ var c = {a:{class:"bw-sign" },c:[{c:{a:dopts.atr, c:[content]}}]};
1661
+ //{a:{class:"bw-jumbo"},c:[{c:{ c:"foo"}}]}
1662
+ return bw.html(c);
1663
+ };
1664
+ // ===================================================================================
1665
+ bw.getFile = function (fname,callback_fn, options) {
1666
+ /**
1667
+ bw.getFile(filename,callback)
1668
+ Attempt to load a file.
1669
+ Works both client side and i nodejs.
1670
+ */
1671
+ var dops = {
1672
+ parser : "raw" // valid types are "raw", "JSON", future "CSV", "TSV" or parserFunction
1673
+ };
1674
+
1675
+ dops = optsCopy(dops,options);
1676
+
1677
+ if (_to(fname) != "string") {
1678
+ return "invalid filename";
1679
+ }
1680
+
1681
+ var prs = (dops["parser"]=="JSON") ? JSON.parse : function(s){return s;};
1682
+
1683
+
1684
+ if (bw.isNodeJS() ==true) {
1685
+ var fs = require("fs");
1686
+ fs.readFile(fname, "utf8", function (err, data) { if (err) throw err; callback_fn(prs(data)); });
1687
+ }
1688
+ else // running in a browser
1689
+ {
1690
+ var x = new XMLHttpRequest();
1691
+ x.overrideMimeType("application/json");
1692
+ x.open("GET", fname, true);
1693
+ x.onreadystatechange =
1694
+ function () {if (x.readyState == 4 && x.status == "200") {callback_fn(prs(x.responseText));}};
1695
+ x.send(null);
1696
+ }
1697
+ return "BW_OK";
1698
+ };
1699
+
1700
+ bw.getJSONFile = function (fname,callback_fn) { return bw.getFile(fname,callback_fn,{"parser":"JSON"});};
1701
+
1702
+ bw.copyToClipboard = function(data) {
1703
+ /**
1704
+ bw.copyToClipboard
1705
+ simple copy content to clipboard. (browser only)
1706
+ */
1707
+
1708
+ /*
1709
+ var temp = document.createElement("input");
1710
+ var b = document.getElementsByTagName("body")[0];
1711
+ b.appendChild(temp);
1712
+
1713
+ temp.innerText = data;
1714
+ temp.select();
1715
+ document.execCommand("copy");
1716
+ temp.remove();
1717
+
1718
+
1719
+
1720
+ var temp = document.createElement("input");
1721
+ document.getElementsByTagName("body")[0].append(temp);
1722
+ temp.innerHTML = data;
1723
+ //temp.val(data).select();
1724
+
1725
+ //var temp = document.createElement("input");
1726
+ //var b = document.getElementsByTagName("body")[0];
1727
+ //b.appendChild(temp);
1728
+ //temp.innerText = data;
1729
+ temp.select();
1730
+ document.execCommand("copy");
1731
+ temp.remove();
1732
+ */
1733
+ if (bw.isNodeJS())
1734
+ return;
1735
+ var listener = function (e) {
1736
+ e.clipboardData.setData("text/html", data);
1737
+ e.clipboardData.setData("text/plain", data);
1738
+ e.preventDefault();
1739
+ };
1740
+ document.addEventListener("copy", listener);
1741
+ document.execCommand("copy");
1742
+ document.removeEventListener("copy", listener);
1743
+ };
1744
+
1745
+ // ===================================================================================
1746
+ bw.saveClientFile = function(fname,data) {
1747
+ /**
1748
+ bw.saveClientFile(fname,data) saves data the program the client environtmnet
1749
+ fname is filename to save as
1750
+ data is data to save.
1751
+
1752
+ works both in node and browser.
1753
+ */
1754
+ if (bw.isNodeJS()) {
1755
+ var fs = require("fs");
1756
+ fs.writeFile(fname, data, function (err) {
1757
+ if (err) return bw.log(err);
1758
+ bw.log("error saving ",fname,data);
1759
+ });
1760
+ }
1761
+ else { // we're in a browser
1762
+
1763
+ var saveData = (function () {
1764
+ var a = document.createElement("a");
1765
+ document.body.appendChild(a);
1766
+ a.style = "display: none";
1767
+ return function (data, fname) {
1768
+ var json = JSON.stringify(data),
1769
+ blob = new Blob([json], {type: "octet/stream"}),
1770
+ url = window.URL.createObjectURL(blob);
1771
+ a.href = url;
1772
+ a.download = fname;
1773
+ a.click();
1774
+ window.URL.revokeObjectURL(url);
1775
+ };
1776
+ }());
1777
+ saveData(data,fname);
1778
+ }
1779
+ };
1780
+
1781
+ // ===================================================================================
1782
+ //Timers ... clear / read / fixed number of events
1783
+
1784
+ // ===================================================================================
1785
+ // crude performance measurements
1786
+ var gBWTime = (new Date()).getTime(); //global closure for time. 'cause we always want a gbw gbw time :)
1787
+
1788
+ // ===================================================================================
1789
+ bw.clearTimer = function (message) {
1790
+ /**
1791
+ bw.clearTimer("message")
1792
+ When bitwrench loads its starts a timer which can be checked at any time as a ref running (see bw.readTimer()).
1793
+ bw.clearTimer() clears the timer with optional message.
1794
+ */
1795
+ gBWTime = (new Date()).getTime();
1796
+ if (_to(message) != "undefined")
1797
+ bw.logd(String(message));
1798
+ return gBWTime;
1799
+ };
1800
+
1801
+ // ===================================================================================
1802
+ bw.readTimer = function (message) {
1803
+ /**
1804
+ bw.readTimer("message")
1805
+ When bitwrench loads its starts a page timer which can be checked for how long the page as been running.
1806
+ */
1807
+ var ct = (new Date()).getTime();
1808
+ if (_to(message) != "undefined")
1809
+ bw.logd(String(message));
1810
+ return ct-gBWTime;
1811
+ };
1812
+ bw.clearTimer(); //when bw is loaded, we start the timer.
1813
+
1814
+ // ===================================================================================
1815
+ bw.setIntervalX = function (callback, delay, number_of_repetitions) {
1816
+ /**
1817
+ bw.setIntervalX(callbackFn, delayBtwCalls, repetitions)
1818
+ set a javascript timer to only run a max of N repetions.
1819
+
1820
+ Example:
1821
+ bw.setIntervalX(function(x){console.log(x)},100,5)
1822
+ this will the function 5 times 100ms apart
1823
+ */
1824
+ var x = 0;
1825
+ var intervalID = setInterval(function () {
1826
+ callback(x);
1827
+
1828
+ if (++x >= number_of_repetitions) {
1829
+ clearInterval(intervalID);
1830
+ }
1831
+ }, delay);
1832
+ };
1833
+
1834
+ // ===================================================================================
1835
+ bw.repeatUntil = function (testFn, successFn, failFn, delay, maxReps, lastFn) {
1836
+ /**
1837
+ bw.repeatUntil()
1838
+ repeatUntil runs the supplied testFn every delay milliseconds up until a maxReps number of times.
1839
+ if the test function returns true it runs the successFn and stops the iterations.
1840
+ then the lastFn is called with the params (true, number_of_attempts).
1841
+ lastFn is optional.
1842
+
1843
+ for each time the testFn is called and fails, the failFn() is called.
1844
+
1845
+ After the last rep has been completed the lastFn is called with (with the last testFn result and
1846
+ with the current iteration).
1847
+
1848
+
1849
+ lastFn is optional.
1850
+ failFn is optional
1851
+
1852
+ Example:
1853
+ bw.repeatUntil( myLibsAndDataAreLoaded_fn, renderMyChart, null, 250, 10, null); // attempts to wait until mylib is loaded 10 times before giving up
1854
+
1855
+ */
1856
+ var _count = 0;
1857
+ if (typeof testFn != "function")
1858
+ return "err";
1859
+ if (typeof delay != "number")
1860
+ delay = 250; // 250ms
1861
+ if (typeof maxReps != "number")
1862
+ maxReps = 1; // run 1 time.
1863
+
1864
+ var _testFn = testFn;
1865
+ var _successFn = (typeof successFn == "function") ? successFn : function () {};
1866
+ var _failFn = (typeof failFn == "function") ? failFn : function () {};
1867
+ var _lastFn = (typeof lastFn == "function") ? lastFn : function () {};
1868
+
1869
+ var _f = function () {
1870
+ var success = _testFn();
1871
+ if (true == success) {
1872
+ _successFn();
1873
+ _lastFn(true, _count);
1874
+ }
1875
+ else {
1876
+ _failFn();
1877
+
1878
+ if (_count >= maxReps) {
1879
+ _lastFn(success, _count);
1880
+ }
1881
+ else {
1882
+ _count++;
1883
+ window.setTimeout(_f, delay);
1884
+ }
1885
+ }
1886
+ };
1887
+ _f();
1888
+ };
1889
+ // =============================================================================================
1890
+ /*
1891
+ bw.htmlDataToImg = function(data, opts) {
1892
+ /**
1893
+ htmlDataToImg(data, opts) // takes a 2D array of numbers and render as an image
1894
+ each data point must evaluate to a Number or be a function which will be called with its positional arguments and must return a number.
1895
+
1896
+ OR
1897
+
1898
+ function can be a string as long as it returns a valud HTML color prefixed with "#"
1899
+
1900
+ e.g.
1901
+ "#123"
1902
+ "#112233"
1903
+
1904
+ e.g.:
1905
+ function (return 23)
1906
+ function(x,y) { return x+y;}
1907
+
1908
+ * /
1909
+ var dopts = {
1910
+ outputType : "canvas" , // "table" | "divs" | "svg"
1911
+ colorMode : "auto", // use greyscale map
1912
+ colorStretch: 1.0
1913
+ }
1914
+
1915
+ dopts = optsCopy(dopts,opts);
1916
+ // if (_to(dopts["colorMapFn"]) != "function")
1917
+ // dopts["colorMapFn"] = function(x){var c= mapScale(x,0,255,0,255,true).}
1918
+
1919
+
1920
+
1921
+ }
1922
+ */
1923
+ // =============================================================================================
1924
+ bw.naturalCompare = function (as, bs){
1925
+ /**
1926
+ bw.naturalCompare(a,b) {
1927
+ bw.naturalCompare() is a function which can be passed to an array sort to provide natural sorting of mixed array elements.
1928
+
1929
+ [3,4,2,1,"10","111","foo","bar","01","this123","This123", "848"].sort()
1930
+ vs
1931
+ [3,4,2,1,"10","111","foo","bar","01","this123","This123", "848"].sort(bw.naturalCompare)
1932
+
1933
+ it is the default sort for bw.sortHTMLTable()
1934
+
1935
+ */
1936
+ //https://www.webdeveloper.com/forum/d/254726-sorting-alphanumeric-array (taken from here) see also
1937
+ //using .localCompare() in newer versions of JS
1938
+
1939
+ var a, b, a1, b1, i= 0, L, rx= /(\d+)|(\D+)/g, rd= /\d/;
1940
+ if(isFinite(as) && isFinite(bs)) return Math.sign(as - bs);
1941
+ a= String(as).toLocaleLowerCase();
1942
+ b= String(bs).toLocaleLowerCase();
1943
+ if(a=== b) return (as > bs) ? 1 : 0;
1944
+ if(!(rd.test(a) && rd.test(b))) return a> b? 1:-1;
1945
+ a= a.match(rx);
1946
+ b= b.match(rx);
1947
+ L= a.length> b.length? b.length:a.length;
1948
+ while(i<L){
1949
+ a1= a[i];
1950
+ b1= b[i++];
1951
+ if(a1!== b1){
1952
+ if(isFinite(a1) && isFinite(b1)){
1953
+ if(a1.charAt(0)=== "0") a1= "." + a1;
1954
+ if(b1.charAt(0)=== "0") b1= "." + b1;
1955
+ return a1 - b1;
1956
+ }
1957
+ else return a1> b1? 1:-1;
1958
+ }
1959
+ }
1960
+ return Math.sign(a.length - b.length);
1961
+ };
1962
+
1963
+ // =============================================================================================
1964
+ bw.sortHTMLTable = function (table, col, dir, sortFunction) {
1965
+ /**
1966
+ bw.sortHTMLTable(table, column, optionalSortFunction).
1967
+
1968
+ sort any HTML table active in the DOM
1969
+ table must be a valid DOM table element or CSS selector (first element is used)
1970
+
1971
+ default uses string compare. but can pass in a function
1972
+ sortFunc(a,b,col) // a and b are the cells to compare, col is optional info on what column this is
1973
+ */
1974
+
1975
+ var rows, switching, i, x, y, shouldSwitch;
1976
+ var sortF = _to(sortFunction) == "function" ? sortFunction : bw.naturalCompare;
1977
+
1978
+ table = bw.DOM(table)[0];
1979
+
1980
+ dir = (dir==true) || (dir=="up") ? true : false;
1981
+
1982
+ switching = true;
1983
+ col = _to(col) == "number" ? col : 0; //default sort on left most column
1984
+
1985
+ //Make a loop that will continue until no switching has been done
1986
+ while (switching) {
1987
+ //start by saying: no switching is done:
1988
+ switching = 0;
1989
+ rows = table.getElementsByTagName("TR");
1990
+ /*Loop through all table rows (except the first, which contains table headers):*/
1991
+ for (i = 1; i < (rows.length - 1); i++) {
1992
+ //start by saying there should be no switching:
1993
+ shouldSwitch = 0;
1994
+ /*Get the two elements you want to compare,
1995
+ one from current row and one from the next:*/
1996
+ x = rows[i].getElementsByTagName("TD")[col].innerHTML;
1997
+ y = rows[i + 1].getElementsByTagName("TD")[col].innerHTML;
1998
+
1999
+ //check if the two rows should switch place:
2000
+
2001
+ shouldSwitch = (dir) ? sortF(x,y,col) > 0 : sortF(x,y,col) < 0;
2002
+ if (shouldSwitch)
2003
+ break;
2004
+ }
2005
+
2006
+ if (shouldSwitch) {
2007
+ //If a switch has been marked, make the switch and mark that a switch has been done:
2008
+ rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
2009
+ switching = true;
2010
+ }
2011
+ }
2012
+ };
2013
+
2014
+ // =============================================================================================
2015
+ bw.sortTableDispatch = function (item,fn) {
2016
+ /**
2017
+ bw.sortTableDispatch(el) is used to bind sorting functions to tables generated by bw.htmlTable(....)
2018
+ item must be a valid DOM element or id.
2019
+ */
2020
+ var i;
2021
+
2022
+ item = bw.DOM(item)[0];
2023
+
2024
+ if (_to(item).substr(0,4) != "html")
2025
+ return false; //something not right about this table element
2026
+
2027
+ var index=0,dir;
2028
+ var cols = item.parentElement.getElementsByTagName("th");
2029
+ //update which tab selected
2030
+ for (i=0; i< cols.length; i++) {
2031
+ if (cols[i] == item) { // selected tab logic
2032
+ index = i;
2033
+ dir = bw.DOMClass(cols[i],"bw-table-sort-upa") ; // ifthe current col is already up..
2034
+ if (dir) {
2035
+ bw.DOMClass(cols[i],"bw-table-sort-upa", "bw-table-sort-dna" );
2036
+ }
2037
+ else { //dna or xxa
2038
+ if (bw.DOMClass(cols[i],"bw-table-sort-dna")) {
2039
+ bw.DOMClass(cols[i],"bw-table-sort-dna", "bw-table-sort-upa" );
2040
+ } else
2041
+ bw.DOMClass(cols[i],"bw-table-sort-xxa", "bw-table-sort-upa" );
2042
+ }
2043
+ }
2044
+ else{ // its not the selected column so we clear the up / down arrow
2045
+ bw.DOMClass(cols[i],"bw-table-sort-upa","");
2046
+ bw.DOMClass(cols[i],"bw-table-sort-dna","");
2047
+ bw.DOMClass(cols[i],"bw-table-sort-xxa","bw-table-sort-xxa");
2048
+ }
2049
+
2050
+ }
2051
+ bw.sortHTMLTable(item.parentElement.parentElement.parentElement,index,dir,fn);
2052
+ };
2053
+ // ===================================================================================
2054
+ /**
2055
+ bw.function dispatch for DOM elements..
2056
+
2057
+ the bw.fnRegistry{} is a dict of user supplied functions are assigned IDs by bitwrench. Using these IDs one can call the functions which is useful in DOM string contexts such as makeHTMLTable() or buildHTMLObjStr().
2058
+ */
2059
+ var _fnRegistry = {};
2060
+ var _fnIDCounter = 0;
2061
+
2062
+
2063
+ bw.funcRegister = function (fn, forceName) {
2064
+ /**
2065
+ bw.funcRegister()
2066
+ register a function to be called by iD.
2067
+ fn is any function or can be anonymous function.
2068
+ (optional) forceName forces the returned ID used to be forceName. forceName must be only alpha and numeric chars.
2069
+ forceName is useful when declaring static HTML content and one wants to use the bwFunctionDispatch but before bitwrench has been loaded or run.
2070
+
2071
+ In this case in the static code call like this:
2072
+
2073
+ <div class="..." onclick="bw.funcGetById('myFnName')(this)"> regular html content goes here </div>
2074
+ <script ..>
2075
+ function superDuperFunctionCode (a) { .... code for my function ... };
2076
+ bw.funcRegister(superDuperFunctionCode,"myFnName"); //now when the element is clicked on superDuperFunctionCode() will be called.
2077
+ </script>
2078
+
2079
+ */
2080
+ var fnID = "class_bwfn_" + _fnIDCounter;
2081
+ _fnIDCounter++;
2082
+ fnID = _to(forceName) == "string" ? forceName : fnID;
2083
+ fnID.trim();
2084
+ _fnRegistry[fnID] = fn;
2085
+ return fnID;
2086
+ };
2087
+
2088
+ bw.funcUnregister = function (fnID) {
2089
+ /**
2090
+ bw.funcUnregister(fnID)
2091
+ remove a function from the bitwrench dispatch registry
2092
+ */
2093
+ if (fnID in _fnRegistry)
2094
+ delete _fnRegistry[fnID];
2095
+ };
2096
+
2097
+ bw.funcGetById = function(fnID,errFn) {
2098
+ /**
2099
+ bw.funcGetById(fnId, errFn)
2100
+ allows a function to be exectued by its bw function ID.
2101
+ bw.funcGetById(myId)(... args ...)
2102
+
2103
+ errFn is optional function to call if fnID is not found.
2104
+
2105
+ example:
2106
+ var myFunc = bw.getFuncById("myFuncID"); // function must already be registered.
2107
+ */
2108
+ fnID = String(fnID);
2109
+ if (fnID in _fnRegistry)
2110
+ return _fnRegistry[fnID];
2111
+ else {
2112
+ var _id = fnID;
2113
+ return (_to(errFn) == "function") ? errFn : function(){bw.log(_id,"bw.funcGetById(): unregistered fn error");} ;
2114
+ }
2115
+ };
2116
+
2117
+ bw.funcGetDispatchStr = function (fnID, argstring) {
2118
+ /**
2119
+ bw.funcGetDispatchStr(fnID, argString)
2120
+ create a string suitble for use in DOM element dispatch. note argstring is a literal so variables must be reduce to their values.
2121
+ see bw.funcRegister() for getting valid IDs for user supplied functions.
2122
+
2123
+ example: bw.funcGetDispatchStr("myFuncID","param1,param2")
2124
+ */
2125
+
2126
+ switch (_to(argstring)) {
2127
+ case "string" :
2128
+ case "number" :
2129
+ argstring = String(argstring);
2130
+ break;
2131
+ case "array" :
2132
+ argstring = argstring.join(",");
2133
+ break;
2134
+ case "function":
2135
+ argstring = argstring();
2136
+ break;
2137
+ default:
2138
+ argstring = "";
2139
+ }
2140
+
2141
+ return "bw.funcGetById('"+fnID+"')("+argstring+")";
2142
+ };
2143
+
2144
+ bw.funcGetRegistry = function() {
2145
+ return _fnRegistry;
2146
+ };
2147
+
2148
+ // =============================================================================================
2149
+ bw.loremIpsum = function (numChars, startSpot, startWithCapitalLetter) {
2150
+ /**
2151
+ bw.loremIpsum(numChars, startSpot)
2152
+
2153
+ generate a simple string of Lorem Ipsum text (sample typographer's text) of numChars in length.
2154
+
2155
+ if startSpot is supplied, it starts the string at the supplied index e.g. bw.loremIpsum(200, 50)
2156
+ will supply 200 chars of loremIpsum starting at index 50 of the Lorem Ipsum sample text.
2157
+
2158
+ if startWithCapitalLetter == true then the function will capitlize the first character or inject a capital letter if ihe first character isn't a capital letter.
2159
+ default is true;
2160
+
2161
+ Default is a paragraph of lorem ipsum (446 chars)
2162
+ */
2163
+
2164
+ startSpot = _to(startSpot) != "number" ? 0 : Math.round(startSpot);
2165
+
2166
+ var l = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ";
2167
+ startSpot = startSpot % l.length;
2168
+ l= l.substring(startSpot, l.length) + l.substring(0,startSpot);
2169
+
2170
+ if (_to(numChars) != "number")
2171
+ numChars = l.length;
2172
+
2173
+ var i=numChars, s="";
2174
+
2175
+
2176
+ while (i>0) {
2177
+ s+= (i < l.length) ? l.substring(0,i) : l;
2178
+ i-= l.length;
2179
+ }
2180
+ if (s[s.length-1] == " ")
2181
+ s= s.substring(0,s.length-1) + "."; // always end on non-whitespace. "." was chosen arbitrarily.
2182
+ if (startWithCapitalLetter != false) {
2183
+ var c = s[0].toUpperCase();
2184
+ c = c.match(/[A-Z]/) ? c:"M";
2185
+ s = c+s.substring(1,s.length);
2186
+ }
2187
+
2188
+ return s;
2189
+
2190
+ };
2191
+
2192
+ bw.docString = function (s, options) {
2193
+ /**
2194
+ bw.docString(functionAsString, options)
2195
+ returns array of valid docStrings embedded in a string
2196
+
2197
+ @param docType{string} : "jsdoc" | "python" | "custom" (python means triplequote (") 3 times), "custom" means supply delims
2198
+ @param options {delims:[string,string]} : start, stop delimiters (only used if docType set to "custom")
2199
+
2200
+ @return array{strings} : array of captured params
2201
+
2202
+ */
2203
+
2204
+ var dopts = {
2205
+ docType : "jsdoc", // "js doc", "python", "other" (jsdoc is default)
2206
+ delims : ["/**","*/"],
2207
+ parseJSDocParams : false,
2208
+ dropLeadin : false // removes lead-in whitespace or floating single * on each line e.g. " * @mycomment" ==> "@mycomment"
2209
+ };
2210
+ dopts = optsCopy(dopts,options);
2211
+
2212
+ var _es = function (str) {return str.replace(/(?=[\\^$*+?.()|{}[\]])/g, "\\");}; // do escape of regex chars
2213
+
2214
+ dopts["delims"] = bw.choice(dopts["docType"],{
2215
+ "jsdoc" : ["/**","*/"],
2216
+ "python": ["\"\"\"","\"\"\""], // old regex: /["]{3}([\s\S]*?)["]{3}/ig
2217
+ "jspy" : ["/**\"\"\"","\"\"\"*/"] // js && python
2218
+ },dopts["delims"]);
2219
+
2220
+ var c = (_to(s)=="function") ? s.toString() : String(s);
2221
+ var r = [];
2222
+
2223
+ try {
2224
+ var re = (new RegExp( _es(dopts["delims"][0])+ "\\s*\\n*([^\\*]|(\\*(?!\\/)))*" +_es(dopts["delims"][1]),"ig")); // "([\\s\\S]*?)"
2225
+ r = c.match(re);
2226
+ }
2227
+ catch (e) {bw.log(String(e));}
2228
+
2229
+ if (_to(r)=="array") {
2230
+ r = r.map(function(x){return x.substring(dopts["delims"][0].length, x.length-dopts["delims"][1].length);}); // this is an array of the contents of docStrings which can still be multiline in thier own right
2231
+ r = (dopts["dropLeadin"]==true) ? r.map(function(x){return x.split(/[\n\r]/).map(function(y){return bw.trim(y,"left")+"\n";});}) : r; // need to hanlde multiline stuff here
2232
+ }
2233
+ else
2234
+ r=[];
2235
+
2236
+ return r;
2237
+ };
2238
+ // =============================================================================================
2239
+ bw.docStringParseLine = function(s) {
2240
+ /**
2241
+ Parse a single line of a jsdoc string.
2242
+ @param {string} s - line of docstring to parse
2243
+ @return - dict of line contents {source: s, field:string, name:string, description: string, types: type1,type2 }
2244
+ if not a valid doc string line then returns source string only
2245
+ */
2246
+ var r={"source":s, "field" : "", "types":"", "name" :"", "description" : ""};
2247
+ var a = s.replace(/^\s*(\/\*\*?)?|(\*\/)?\s*$/ig,""); // remove the comment markers if still there "/** my comment */"" ==> "my comment"
2248
+ a = a.replace(/^\s*\**\s*/,""); // remove any cruft at beginning of line " * @myParam {}....." ==> "@myParam {}....."
2249
+ if (a.charAt(0) == "@") { // if we have hit a @fieldname parameter we start parsing.
2250
+ // ([str, regex, fieldStr, result{}]) ==> ([str, regex, fieldStr, result{}]) ::> ([str,result{},fieldStr,regex])
2251
+ /*
2252
+ var _tok = function(x){
2253
+ var m = x[0].match(x[1]);
2254
+ if (m != null) {x[4][3]=m[1];}
2255
+ x[0] = x[0].replace(x[1],"");
2256
+ return x;
2257
+ }
2258
+ */
2259
+ //r = [[e,f],[e,f],[e,f],[e,f]].reduce(,_tok);
2260
+
2261
+ var e,x;
2262
+ var t = bw.trim;
2263
+ e =/^@([A-Za-z0-9_<>[\]]*)/i;
2264
+ x = a.match(e);
2265
+ if (x != null) {r["field"] = t(x[1]);} else return r; // didn't match... opt out here
2266
+ a = a.replace(e,"");
2267
+
2268
+ e = /^\s*\{([A-Za-z0-9_|\s,.\-+!@#$%^&*()=[\]]*)\}/i;
2269
+ x = a.match(e);
2270
+ if (x != null) {r["types"]=t(x[1]);} // types is optional..
2271
+ a = a.replace(e,"");
2272
+
2273
+ e = /^\s*([\S]*)/i;
2274
+ x = a.match(e);
2275
+ if (x != null) {r["description"]=t(x[1]);} //
2276
+ a = a.replace(e,"");
2277
+
2278
+ e = /^\s*([\S]*)/i;
2279
+ x = a.match(e);
2280
+ if (x != null) {r["name"]=t(x[1]);} //
2281
+ a = a.replace(e,"");
2282
+
2283
+ // descrpition ==> name: "" description : "description"
2284
+ // description we we ==> name: "" description : "description we we "
2285
+ // name - description we we ==> name: "name" descrpition : "description we we"
2286
+ // - description we we ==> name: "" description : "description we we"
2287
+ if (r["name"].match(/^\s*-+\s*/) != null) {
2288
+ r["name"] = r["description"];
2289
+ r["description"] = t(a);
2290
+ } else {
2291
+ r["description"] = r["description"]+" "+r["name"]+" "+t(a);
2292
+ r["name"] ="";
2293
+ }
2294
+ }
2295
+ return r;
2296
+ };
2297
+ // =============================================================================================
2298
+ bw.docStringParse = function(s) {
2299
+ /**
2300
+ @method bw.parseJsDocString() parse and extract info from a jsdoc style comment. expects there to be only a single docString comment
2301
+ @description docStringParse parses a jsdoc string
2302
+ and returns the paramters as an array which can be formatted for display or interrogtion.
2303
+ @param{string} - a valid js docstring
2304
+
2305
+ @returns An array of triplets [@param, {types}, comment info]
2306
+ */
2307
+
2308
+ /*
2309
+ implementation notes:
2310
+ the parser splits the candidate doc string in to lines.
2311
+
2312
+ Examples:
2313
+
2314
+ * Assign the project to an employee.
2315
+ * @param {Object} - The employee who is responsible for the project. ==> ["@param","object","", "The employee who is resposnsible for the project"]
2316
+ * @param {string} employee.name - The name of the employee.
2317
+ * @param {string} employee.department - The employee's department.
2318
+
2319
+
2320
+ */
2321
+
2322
+
2323
+ s=bw.docString(s)[0];
2324
+ var a = s.split("\n");
2325
+ //console.log(a);
2326
+ var i,r=[bw.docStringParseLine(a[0])];
2327
+ for (i=1;i<a.length;i++) {
2328
+ var l = bw.docStringParseLine(a[i]);
2329
+ if (l["field"]=="") { // nothing parseable...
2330
+ if (r[r.length-1]["field"]=="") {
2331
+ r[r.length-1]["source"] += l["source"];
2332
+ } else
2333
+ r[r.length-1]["description"] += l["source"];
2334
+ } else r.push(l);
2335
+ }
2336
+ return r;
2337
+ };
2338
+ // =============================================================================================
2339
+ bw.isHexStr = function (str, allowChars) {
2340
+ /**
2341
+ isHexStr() returns a number of hex digits found or false if non-hex string.
2342
+ allow is an optional string of characters "-+."etc to permit in the string.
2343
+ the allow characters are not counted in the result
2344
+
2345
+ examples:
2346
+ bw.isHEXStr("123a") ===> 4
2347
+ bw.isHEXStr("12-3a") ===> false
2348
+ bw.isHEXStr("12-3a","-") ===> 4
2349
+ */
2350
+ if ( _to(str) == "string") {
2351
+ str = str.replace(new RegExp("["+allowChars+"]","g"),"");
2352
+ var isHexReg = new RegExp("^[0-9A-Fa-f]{"+str.length+"}$");
2353
+ return (isHexReg.test(str) == true) ? str.length : false;
2354
+ }
2355
+ return false;
2356
+ };
2357
+
2358
+ // =============================================================================================
2359
+ //bw.__monkey_patch_is_nodejs__ = "ignore"; //used in test suites. use carefully. only acceptable values are true, false, "ignore"
2360
+ bw.__monkey_patch_is_nodejs__ = new (function() { var _t="ignore"; this.set=function(x){_t = _toa(x,"boolean",x,"ignore");}; this.get=function(){return _t;}; return this;});
2361
+
2362
+ bw.isNodeJS = function () {
2363
+ /**
2364
+ bw.isNodeJS() ==> returns true if running in node environment (else browser)
2365
+ */
2366
+ if (bw.__monkey_patch_is_nodejs__.get() != "ignore")
2367
+ return bw.__monkey_patch_is_nodejs__.get();
2368
+ return (typeof module !== "undefined" && module.exports) !== false; //a hack will fix later
2369
+ };
2370
+ //console.log("=====",bw.isNodeJS())
2371
+
2372
+ // =============================================================================================
2373
+ bw.fixNum = function(num,digits) {
2374
+ /**
2375
+ bw.fixedNum(num,digits)
2376
+
2377
+ Truncate a number at digits number of places.
2378
+ bw.fixNum(1.2345,2) ===> 1.23
2379
+ bw.fixNum(234.32,-2) ===> 200
2380
+ */
2381
+ num = Number(num);
2382
+
2383
+ if (isNaN(num))
2384
+ return NaN;
2385
+
2386
+ digits = _to(digits) == "number" ? digits : 3;
2387
+ num *= Math.pow(10,digits);
2388
+
2389
+ //num = Math.trunc(num);
2390
+ num = (num > 0) ? Math.floor(num) : Math.ceil(num); // some browsers don't support Math.trunc()
2391
+
2392
+ num /= Math.pow(10,digits);
2393
+ return num;
2394
+ };
2395
+
2396
+ // =============================================================================================
2397
+ bw.multiArray = function (value, dims) {
2398
+ /**
2399
+ bw.multiArray(value, dims)
2400
+
2401
+ return a multidimensional array where all cells are initialized to value.
2402
+
2403
+ bw.multiArray(0,[4,5]) // returns 4x5 array of 0s
2404
+ bw.multiArray("test",[4,5]) // returns 4x5 array of "test"
2405
+
2406
+ this shorthand is available for single dim arrays
2407
+ bw.multiArray(0,5) ===> returns 5x1 array of 0s
2408
+
2409
+ bw.multiArray also accepts functions
2410
+
2411
+ bw.multiArray(bw.random, [3,4]) ===> creates 3x5 array of random #s btw 0..100
2412
+
2413
+ bw.multiArray(function(){return (new Date()).getTime();},[4,6] ) ==> returns values based on the Javascript date
2414
+ */
2415
+
2416
+ var v = function() { return (_to(value) == "function") ? value(): value;};
2417
+ dims = _to(dims) == "number" ? [dims] : dims;
2418
+
2419
+ var _array = function(a,dim) {
2420
+ if(dim < dims.length) {
2421
+ for(var i=0; i<dims[dim]; i++) {
2422
+ a[i]= (dim== dims.length -1) ? v() : _array([],dim+1);
2423
+ }
2424
+ return a;
2425
+ }
2426
+ };
2427
+ return _array([],0);
2428
+ };
2429
+
2430
+ // =============================================================================================
2431
+ bw.clip = function (data, min, max) {
2432
+ /**
2433
+ @method: bw.clip(data, min, max) clips data in between min and max.
2434
+
2435
+ Examples:
2436
+ bw.clip(5,2,20) ==> 5 // already in range
2437
+ bw.clip(1,2,20) ==> 2 // less than the min value so clips to min value
2438
+ bw.clip([1,4,8,35], 2, 20) ==> [2,4,5,20]
2439
+ */
2440
+ var l = min < max ? min : max;
2441
+ var h = max > min ? max : min;
2442
+
2443
+ if (_to(data) == "array") {
2444
+ return data.map(function(x){ return (x < l) ? l : ((x > h) ? h : x);});
2445
+ }
2446
+ else
2447
+ return (data < l) ? l : ((data > h) ? h : data);
2448
+ };
2449
+
2450
+ // =============================================================================================
2451
+ bw.mapScale = function (x, in0, in1, out0, out1, options) {
2452
+ /**
2453
+ @method: bw.mapScale()
2454
+
2455
+ Map an input value x in its natural range in0...in1 to the output space out0...out1 with optional clipping
2456
+ expScale allows sigmoidal warping to stretch input values contrained to a small range. (floating point scale factor)
2457
+ x can be either a number or array of numbers.
2458
+
2459
+ if options["clip"] = false, then mapScale will extrapolate outside of out0,out1
2460
+
2461
+ //this is the function that oficially started bitwrench..
2462
+ */
2463
+ var dopts = {
2464
+ clip : true,
2465
+ expScale : false
2466
+ };
2467
+
2468
+ dopts = optsCopy ( dopts, options);
2469
+
2470
+ if (in0==in1)
2471
+ return x;
2472
+ out0 = _toa(out0, "number", out0, 0);
2473
+ out1 = _toa(out1, "number", out1, 1);
2474
+
2475
+ var ms= function (z) {
2476
+ if (dopts["expScale"]) {
2477
+ var y = ((z-((in1+in0) / 2.0)) / (in1 - in0) ) * dopts["expScale"];
2478
+ z = ((out1-out0)*(1/(1+Math.exp(-y)))) + out0;
2479
+ }
2480
+ else
2481
+ z = (((z-in0)/(in1-in0))*(out1-out0))+out0;
2482
+
2483
+ if (dopts["clip"])
2484
+ z= bw.clip(z,out0,out1);
2485
+ return z;
2486
+ };
2487
+
2488
+ if (_to(x) == "number")
2489
+ return ms(x);
2490
+ return x.map(ms);
2491
+ };
2492
+
2493
+ // =============================================================================================
2494
+ //https://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript
2495
+ bw.padNum = function(x, width, options) {
2496
+ /**
2497
+ @description bw.padnum() takes a number and pads left pads (default is '0'). padNum also accepts strings so
2498
+
2499
+ bw.padNum(123,5) ==> " 123"
2500
+ bw.padNum(1234,5) ==> " 1234"
2501
+ bw.padNum("foo",5)==> " foo"
2502
+ @param x {number}
2503
+ @return {string} padded number
2504
+ */
2505
+ var dopts = {
2506
+ pad : " "
2507
+ };
2508
+ dopts = optsCopy(dopts, options);
2509
+ x = String(x);
2510
+ return (x.length >= width) ? x : new Array(width - x.length+1).join(dopts["pad"]) + x;
2511
+ };
2512
+ // =============================================================================================
2513
+ bw.trim = function (s, dir) {
2514
+ /**
2515
+ @description bw.trim() trims whitespace from string on either left, right, or both. (cross browser works before IE8)
2516
+ @param s {string} : a string to trim white space on
2517
+ @param dir {"left" | "right" | "both" | "none"} : trim white space on left only, right only or both sides, or no trim (default is both)
2518
+ */
2519
+ var t = bw.choice(
2520
+ dir,
2521
+ {
2522
+ "left" : /^[\s\uFEFF\xA0\n]+/g,
2523
+ "right" : /[\s\uFEFF\xA0\n]+$/g,
2524
+ "none" : /(?!)/ // useful for programmatic scenarios (eg. [....].map ) where not all of the entries should be trimmed.
2525
+ },
2526
+ /^[\s\uFEFF\xA0\n]+|[\s\uFEFF\xA0\n]+$/g
2527
+ );
2528
+ return String(_toa(s,"undefined","",s)).replace(t,"");
2529
+ };
2530
+
2531
+ // =============================================================================================
2532
+ bw.padString = function (s, width, dir, options) {
2533
+ /**
2534
+ @description bw.padString() takes a string and pads it to the specified number of chars either left or right or centered.
2535
+ */
2536
+ var dopts = {
2537
+ pad : " ",
2538
+ trimDir : "both" // pre-trim the input string: "left", "right", "both", "none"
2539
+ };
2540
+ dopts = optsCopy(dopts, options);
2541
+
2542
+ s = String(s);
2543
+ var x = bw.trim(s,dopts["trimDir"]);
2544
+ var p = (width > x.length ) ? (width - x.length+1) : 0 ; // total padding
2545
+ var q = bw.choice(dir,
2546
+ {
2547
+ "left" : [p,0],
2548
+ "right" : [0,p],
2549
+ "center" : [Math.round(p/2), (p-Math.round(p/2)+1)]
2550
+ },
2551
+ [0,0]
2552
+ );
2553
+ return ((new Array(q[0])).join(dopts["pad"]))+x+(new Array(q[1])).join(dopts["pad"]);
2554
+ };
2555
+
2556
+ // =============================================================================================
2557
+ bw.random = function(rangeBegin, rangeEnd, options) {
2558
+ /**
2559
+ @method: random
2560
+
2561
+ Return a random number between rangeBegin and RangeEnd (inclusive)
2562
+ default is 0,100
2563
+
2564
+ options
2565
+ {
2566
+ setType : "int"
2567
+ dims : false | number | [ , , ] // selector for dimensions
2568
+ }
2569
+
2570
+ options
2571
+ setType:
2572
+ "int" ==> return an integer (default)
2573
+ "float" or "number" ==> return floating point number
2574
+
2575
+ dims
2576
+ false or ommitted ==> return a single number
2577
+ 5 ==> return a 5x1 array of random numbers
2578
+ [3,5,2] ==> return a 3x5x2 array of random numbers
2579
+
2580
+ example:
2581
+ bw.random() ==> returns a number btw 0,100
2582
+ bw.random(-4,4,{setType: "float", dims[4,5]}) ==> returns a 3x5 array of floating pt numbers btw -4,4
2583
+
2584
+ see also prandom for psuedorandom numbers
2585
+
2586
+ */
2587
+ rangeBegin = _to(rangeBegin) == "number" ? rangeBegin : 0;
2588
+ rangeEnd = _to(rangeEnd) == "number" ? rangeEnd : 100;
2589
+
2590
+ var dopts = {
2591
+ setType : "int",
2592
+ dims : false // if dims is array e.g. [3,4,5] returns random elements array
2593
+ };
2594
+
2595
+ dopts = optsCopy(dopts,options);
2596
+
2597
+ var _rnd = function () {
2598
+ var n = 0;
2599
+
2600
+ dopts.setType = ["int","float","number"].indexOf(dopts.setType) == -1 ? "int" : dopts.setType;
2601
+
2602
+ if (rangeEnd < rangeBegin ) {
2603
+ rangeBegin ^= rangeEnd; rangeEnd ^= rangeBegin; rangeBegin ^= rangeEnd;
2604
+ }
2605
+ n = ((Math.random() * (rangeEnd-rangeBegin)) + rangeBegin);
2606
+
2607
+ return (dopts.setType == "int") ? Math.round(n) : n;
2608
+ };
2609
+
2610
+ if ((_to(dopts["dims"]) == "array") || (_to(dopts["dims"])== "number"))
2611
+ return bw.multiArray( _rnd, dopts["dims"]);
2612
+
2613
+ return _rnd();
2614
+ };
2615
+
2616
+ // =============================================================================================
2617
+ bw.prandom = function (rangeBegin,rangeEnd,seed, options) {
2618
+ /**
2619
+ prandom - generate a psuedo random number from internal hash function in a given range
2620
+ */
2621
+ rangeBegin = _to(rangeBegin) == "number" ? rangeBegin : 0;
2622
+ rangeEnd = _to(rangeEnd) == "number" ? rangeEnd : 100;
2623
+
2624
+ var dopts = {
2625
+ setType : "int",
2626
+ dims : false // if dims is array e.g. [3,4,5] returns random elements array
2627
+ };
2628
+
2629
+ dopts = optsCopy(dopts,options);
2630
+ var _cseed = seed;
2631
+ var _rnd = function () {
2632
+ var n = 0;
2633
+
2634
+ dopts.setType = ["int","float","number"].indexOf(dopts.setType) == -1 ? "int" : dopts.setType;
2635
+
2636
+ if (rangeEnd < rangeBegin ) {
2637
+ rangeBegin ^= rangeEnd; rangeEnd ^= rangeBegin; rangeBegin ^= rangeEnd;
2638
+ }
2639
+ n = (((bw.hashFnv32a("start string",_cseed) & 0xffff)/(65536)) * (rangeEnd-rangeBegin)) + rangeBegin;
2640
+
2641
+ _cseed = (dopts.setType == "int") ? Math.round(n) : n;
2642
+ return (dopts.setType == "int") ? Math.round(n) : n;
2643
+ };
2644
+
2645
+ if ((_to(dopts["dims"]) == "array") || (_to(dopts["dims"])== "number"))
2646
+ return bw.multiArray( _rnd, dopts["dims"]);
2647
+
2648
+ return _rnd();
2649
+
2650
+ };
2651
+ // =============================================================================================
2652
+
2653
+
2654
+ bw.hashFnv32a= function (str, seed, returnHexStr) {
2655
+ /**
2656
+ @method Calculate a 32 bit FNV-1a hash
2657
+ Found here: https://gist.github.com/vaiorabbit/5657561
2658
+ Ref.: http://isthe.com/chongo/tech/comp/fnv/
2659
+
2660
+ @param {string} str the input value
2661
+
2662
+ @param {integer} [seed] optionally pass the hash of the previous chunk
2663
+
2664
+ @param {boolean} [asString=false] set to true to return the hash value as
2665
+ 8-digit hex string instead of an integer
2666
+
2667
+ @returns {integer | string}
2668
+ */
2669
+ /*jshint bitwise:false */
2670
+ var i, l,
2671
+ hval = (typeof seed == "undefined") ? 0x811c9dc5 : seed;
2672
+
2673
+ for (i = 0, l = str.length; i < l; i++) {
2674
+ hval ^= str.charCodeAt(i);
2675
+ hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
2676
+ }
2677
+ if( returnHexStr ){
2678
+ // Convert to 8 digit hex string
2679
+ return ("0000000" + (hval >>> 0).toString(16)).substr(-8);
2680
+ }
2681
+ return hval >>> 0;
2682
+ };
2683
+
2684
+ // =============================================================================================
2685
+ bw.CSSMakeTheme = function(color) {
2686
+ /**
2687
+ makeThemeCSS (color)
2688
+
2689
+ makes a CSS theme color palettte based on the supplied color which is exported as a css style
2690
+
2691
+ TODO
2692
+ */
2693
+ var c = bw.colorRgbToHsl( bw.colorParse(color));
2694
+
2695
+ var p = "bw-theme-";
2696
+ var thm = ["l5","l4","l3","l2","l1","d1","d2","d3","d4","d5"].map(function(x){return p+x;});
2697
+ var im = " !important";
2698
+ thm = thm.map(function(x,i){return [x,[["color", ((i<5)?"#000" : "#fff")+im ],["background-color",c + im] ]]; });
2699
+
2700
+ return thm;
2701
+
2702
+ };
2703
+ // =============================================================================================
2704
+ bw.CSSSimpleStyles = function(appendToHead, options) {
2705
+ /*
2706
+ bw.CSSimpleStyles(appendToHead,options)
2707
+
2708
+ Generate simple styles for bitwrench.
2709
+ write a quick grid style sheet for quick n dirty layout. See docs for examples.
2710
+
2711
+ appendToHead ==> if true, attempts to append to HTML <head> (only writes if not already present)
2712
+ options: {
2713
+ "basics" : "load" // if set to "load will load some global constants for html, body, font-family", set to false to leave these unchanged.
2714
+ "exportCSS": false // if true will wrap the output css in "script" tags.
2715
+ "id" : "bw-default-styles" // id assigned to the script tag, used for preventing multiple loading in a browser page
2716
+ }
2717
+
2718
+ */
2719
+
2720
+ var dopts = {
2721
+ "globals" : false,
2722
+ "id" : "bw-default-styles",
2723
+ "exportCSS" : false,
2724
+ "colorset" : {"color" : "#000", "background-color" :"#ddd", "active" : "#222"},
2725
+ "pretty" : false, //make easy to read
2726
+ "themes" : // built-in primitive themes
2727
+ [ // must be valid CSS keys / values
2728
+ [".bw-thm-light" , {"color": "#020202 !important;", "background-color": "#e2e2e2 !important;"}],
2729
+ [".bw-thm-dark" , {"color": "#e2e2e2 !important;", "background-color": "#020202 !important;"}],
2730
+ ]
2731
+ };
2732
+
2733
+ var s ="\n", i;
2734
+ //var i,j,k,l;
2735
+ var _r = bw.fixNum;
2736
+ var rl = bw.makeCSSRule;
2737
+ dopts = optsCopy(dopts,options);
2738
+
2739
+ var defs = { // defaults
2740
+ defGlobals: {"box-sizing": "border-box"},
2741
+ defContainer: {"height": "100%", "width":"90%", "margin": "0 auto", "padding-left": "2%","padding-right":"2%","left":"0","top":"1%","box-sizing":"border-box"},
2742
+ defFontSerif: {"font-family": "Times New Roman, Times, serif"},
2743
+ defFontSansSerif: {"font-family": "Arial, Helvetica, sans-serif" }
2744
+ };
2745
+
2746
+ if (dopts["globals"] == "load") {
2747
+ s+= rl([["html","body"],defs.defContainer]);
2748
+ s+= rl(["*"+defs.defFontSansSerif]);
2749
+ }
2750
+
2751
+
2752
+
2753
+ var d = [
2754
+ //globals
2755
+ ["*", defs.defGlobals],
2756
+ [".bw-def-page-setup" , defs.defContainer],
2757
+ [".bw-font-serif" , defs.defFontSerif],
2758
+ [".bw-font-sans-serif", defs.defFontSansSerif],
2759
+ "\n",
2760
+ //text handling
2761
+ [".bw-left", {"text-align": "left"}],
2762
+ [".bw-right", {"text-align": "right"}],
2763
+ [".bw-center", {"text-align": "center", "margin": "0 auto"}],
2764
+ [".bw-justify", {"text-align": "justify"}],
2765
+ [".bw-code", {"font-family":"monospace", "white-space":"pre-wrap"}],
2766
+ [".bw-pad1", {"padding-left": "1%", "padding-right":"1%"}],
2767
+ "\n",
2768
+ //tables
2769
+ [".bw-table", {"border-collapse":"collapse", "border-spacing": "0", "border":"1px solid #444"}],
2770
+ [".bw-table th", {"background-color": "#bbb", "padding": "4px","border":"1px solid #444"}],
2771
+ [".bw-table td", {"padding": "4px","border":"1px solid #444"}],
2772
+ [".bw-table-stripe tr:nth-child(even)" , {"background-color":"#f0f0f0"}], // striped rows
2773
+ [".bw-table tr td:first-child", {"font-weight": "700"}], // make first col bold
2774
+ [".bw-table-border-round", {"border-radius":"2px"}],
2775
+ [".bw-table-sort-upa::after", {"content": "\"\\2191\""}], // table sort arrow up (when visible arrows chosen)
2776
+ [".bw-table-sort-dna::after", {"content": "\"\\2193\""}], // table sort arrow dn (when visible arrows chosen)
2777
+ [".bw-table-sort-xxa::after", {"content": "\"\\00a0\""}], // table sort space (when visible arrows chosen)
2778
+ "\n",
2779
+ //tabs
2780
+ [".bw-tab-item-list", { "margin": 0, "padding-inline-start":0}],
2781
+ [".bw-tab-item",{"display":"inline", "padding-top":"0.5em", "padding-left":"0.75em", "padding-right":"0.75em", "border-top-right-radius":"7px", "border-top-left-radius": "7px"}],
2782
+ [".bw-tab-active", {/* padding-top:4px; padding-left:6px; padding-right:6px; padding-bottom:0; */ "font-weight":"700"}],
2783
+ [".bw-tab:hover",{"cursor": "pointer", "font-weight": 700/* border: 1px solid #bbb; */}],
2784
+ [".bw-tab-content-list", { margin: 0, "padding-top": "0.0em"}],
2785
+ [".bw-tab-content",{ display:"none", "border-radius":0}],
2786
+ [".bw-tab-content, .bw-tab-active", {"background-color": "#ddd", "padding":"0.5em"}],
2787
+ "\n",
2788
+ //accordian
2789
+ [".bw-accordian-container > div", {"padding": "0.5em"}],
2790
+ "\n",
2791
+ //grid setup
2792
+ [".bw-container",{ margin: "0 auto"}],
2793
+ [".bw-row", { width: "100%", display: "block"}],
2794
+ [".bw-row [class^=\"bw-col\"]", { float: "left"}],
2795
+ [".bw-row::after", { content: "\"\"", display: "table", clear:"both"}],
2796
+ [".bw-box-1", {"padding-top":"10px","padding-bottom": "10px", "border-radius": "8px"}],
2797
+ "\n",
2798
+ [".bw-sign", {"position": "inherit", "display": "table", "height": "100%", "width": "100%"}],
2799
+ [".bw-sign > div", {display:"table-cell", "vertical-align" : "middle"}],
2800
+ [".bw-sign > div > div", {"text-align":"center"}],
2801
+ "\n",
2802
+ //misc element controls
2803
+ [".bw-hide", { display: "none"}],
2804
+ [".bw-show", { display: "block"}]
2805
+ ];
2806
+
2807
+ //heading generator
2808
+ [1,2,3,4,5,6].map(function(x){d.push([".bw-h"+x, {"font-size":_r(3.2*Math.pow(.85,x+1))+"rem"}]);});
2809
+
2810
+ d.push("\n");
2811
+ // grid system (generated)
2812
+ for (var k=1; k<=12; k++)
2813
+ d.push([".bw-col-"+k, {width:(_r(k*100/12)+"%")}]);
2814
+
2815
+ d.push("\n");
2816
+ // generate CSS from above rules
2817
+ s+= d.map(function(x){return rl(x,{pretty:dopts.pretty});}).join("\n")+"\n";
2818
+
2819
+ d.push("\n");
2820
+ //primtive in-built color themeing see opts to overide
2821
+ for (i in dopts["colorset"]){
2822
+ s+= ".bw-color-"+i+" {"+i+":" +dopts["colorset"][i]+"}\n";
2823
+ }
2824
+
2825
+ d.push("\n");
2826
+ bw.makeCSS( dopts["themes"]);
2827
+ for (i=0; i< dopts["themes"].length; i++) {
2828
+ s+= rl( dopts["themes"][i]);
2829
+ //s+= bw.makeCSS( dopts["themes"][i])
2830
+ }
2831
+
2832
+ //responsive screen
2833
+ var m = "@media only screen and (min-width: ";
2834
+ s+= m + "540px) {.bw-def-page-setup {width: 96%;}}\n";
2835
+ s+= m + "720px) {.bw-def-page-setup {width: 92%;}}\n";
2836
+ s+= m + "960px) {.bw-def-page-setup {width: 88%;}}\n";
2837
+ s+= m + "1100px){.bw-def-page-setup {width: 86%;}}\n";
2838
+ s+= m + "1600px){.bw-def-page-setup {width: 84%;}}\n";
2839
+
2840
+
2841
+ if (bw.isNodeJS() == false) {
2842
+ var h = bw.DOM("head")[0];
2843
+ var el = document.createElement("style");
2844
+ el.id = dopts["id"];
2845
+ el.textContent = s;
2846
+
2847
+ if (appendToHead && (document.getElementById(dopts["id"]) == null)) // only append once
2848
+ h.appendChild(el);
2849
+ }
2850
+ if (dopts["exportCSS"])
2851
+ s = bw.html(["style",{"id":dopts["id"]},"\n/**\n bitwrench basic css styles\n version: "+bw.version()["version"]+"\n */"+s]);
2852
+ return s;
2853
+ };
2854
+
2855
+
2856
+
2857
+ //================================================================================
2858
+
2859
+ bw.CSSSimpleThemes = function (d,appendToHead) {
2860
+ /**
2861
+ bw.bwSimpleThemes() selects simple (we do mean simple) HTML themes for some basic elements.
2862
+ if d is an number it selects the built-in theme by index (see docs) else if d is a dictionary the elements
2863
+ in d will be converted to a CSS style.
2864
+
2865
+ output is a CSS style. if appendToHead is true or omitted then the theme is appended to the head element.
2866
+ */
2867
+ var s ="",xs={};
2868
+ /*
2869
+ var thm = {
2870
+ defBkgCol : "#333",
2871
+ defCol : "#ddd",
2872
+ col1 : "#555",
2873
+ col2 : "#f0f0f0",
2874
+ brd : "#ddd"
2875
+ };
2876
+
2877
+ var thmCSS =
2878
+ [
2879
+ ["*" , {"background-color": thm.defBkgCol, "color":thm.defCol, "font-family": "sans-serif", "box-sizing":"border-box"}],
2880
+ ["body" , {"margin-top":"1%"}],
2881
+ ["th" , {"background-color":thm.col1}],
2882
+ ["tbody tr:nth-child(even)" , {"background-color": thm.col2}],
2883
+ [["table", "td", "th"] , {"border-collapse":"collapse", "border":"1px solid "+thm.brd}],
2884
+ [["td","th"] , {"padding":"4px"}],
2885
+ [["div","body","button","table","input"] , {"border-radius": "2px"}]
2886
+ // ["div", {"padding-left":"2%", "padding-right":"2%","padding-top":"1%","padding-bottom":"1%"}]
2887
+ ];
2888
+ */
2889
+ var def = [ // default styles
2890
+ {
2891
+
2892
+ css:
2893
+ [
2894
+ ["*" , {"background-color": "#333", "color":"#ddd", "font-family": "sans-serif", "box-sizing":"border-box"}],
2895
+ ["body" , {"margin-top":"1%"}],
2896
+ ["th" , {"background-color":"#555"}],
2897
+ ["tbody tr:nth-child(even)" , {"background-color": "#f0f0f0"}],
2898
+ [["table", "td", "th"] , {"border-collapse":"collapse", "border":"1px solid #ddd"}],
2899
+ [["td","th"] , {"padding":"4px"}],
2900
+ [["div","body","button","table","input"] , {"border-radius": "2px"}]
2901
+ // ["div", {"padding-left":"2%", "padding-right":"2%","padding-top":"1%","padding-bottom":"1%"}]
2902
+ ],
2903
+ },
2904
+ {
2905
+ css:
2906
+ [
2907
+ ["*" , {"background-color": "#f8f8f8", "color":"#111", "font-family": "sans-serif", "box-sizing":"border-box"}],
2908
+ ["body" , {"margin-top":"1%"}],
2909
+ ["th" , {"background-color":"#ddd"}],
2910
+ ["tbody tr:nth-child(even)" , {"background-color":"#ddd"}],
2911
+ [["table", "td", "th"] , {"border-collapse":"collapse", "border":"1px solid #111"}],
2912
+ [["td","th"] , {"padding":"4px"}],
2913
+ [["div","body","button","table","input"] , {"border-radius": "2px"}]
2914
+ // ["div", {"padding-left":"2%", "padding-right":"2%","padding-top":"1%","padding-bottom":"1%"}]
2915
+ ],
2916
+ }
2917
+ ];
2918
+ xs = bw.choice(_to(d),{
2919
+ "object" : d,
2920
+ // "string" : function(){}
2921
+ "number" : ((d>=0) && (d<def.length))?def[d].css:def[0].css
2922
+ },def[0].css);
2923
+
2924
+ s= xs.map(function(y){return bw.makeCSSRule(y,{pretty:false});}).join("\n");
2925
+ if (appendToHead != false) {
2926
+ //var hs = document.getElementById("bw-simple-theme-styles");
2927
+ var hs = bw.DOM("bw-simple-theme-styles");
2928
+ if (hs.length == 0) {// first time
2929
+ //var h = document.getElementsByTagName("head")[0];
2930
+ var h = bw.DOM("head")[0];
2931
+ var el = document.createElement("style");
2932
+ el.id = "bw-simple-theme-styles";
2933
+ el.textContent = s; //note IE8 requires .text=
2934
+ h.appendChild(el);
2935
+ }
2936
+ else { // replace it
2937
+ hs.textContent = s; //note IE8 requires .text=
2938
+ }
2939
+
2940
+ }
2941
+
2942
+ return s;
2943
+ };
2944
+
2945
+ // =============================================================================================
2946
+ bw.selectTabContent = function (item, target) {
2947
+ /**
2948
+ This function is used inside a tab block to show the appropriate content. Note that this is
2949
+ designed to work even if code is emitted as document.getElementById("myTabs").innerHTML = <<generated code..>>
2950
+ or statically written by the programmer.
2951
+
2952
+ note that DOM IDs are not required as selectTabContent() uses DOM path relative logic internally
2953
+
2954
+ <div class="bw-tab-container"> <!-- bw-tab-container -- bw-tab-items (array of items), bw-tab-content (array of content to show) -->
2955
+ <ul class="bw-tab-item-list"> <!-- container for the tabs -->
2956
+ <li class="bw-tab userTab bw-tab-active" onclick="bw.selectTabContent(this)" >Tab 1</li>
2957
+ <li class="bw-tab userTab " onclick="bw.selectTabContent(this)" >Tab 2</li>
2958
+ <li class="bw-tab userTab " onclick="bw.selectTabContent(this)" >Tab 3</li>
2959
+ <li class="bw-tab userTab " onclick="bw.selectTabContent(this)" >Tab 4</li>
2960
+ </ul>
2961
+ <div class="bw-tab-content-list"> <!-- container for the tab content -->
2962
+ <div class="bw-tab-content bw-show" >coontent area 1 </div> <!-- bw-show picks which tab to make active at first -->
2963
+ <div class="bw-tab-content" >content area 2</div>
2964
+ <div class="bw-tab-content" >content area 3</div>
2965
+ <div class="bw-tab-content" >content area 4</div>
2966
+ </div> <!-- end of tab content sect -->
2967
+ </div>
2968
+ */
2969
+ //if (_to(item)=="string")
2970
+ // item = document.getElementById(item);
2971
+ item = bw.DOM(item)[0];
2972
+
2973
+ if (_to(item).substr(0,4) != "html")
2974
+ return false; //unable to set tab content
2975
+ document.gx=item;
2976
+ var i,j,index=0;
2977
+ var cols = item.parentNode.getElementsByTagName("li");
2978
+ //update which tab selected
2979
+ for (i=0; i< cols.length; i++) {
2980
+ if (cols[i] == item) { // selected tab logic
2981
+ index = i;
2982
+ cols[i].className = bw.classStrAddDel(cols[i].className,"bw-tab-active");
2983
+ }
2984
+ else { // unselected tab logic
2985
+ cols[i].className = bw.classStrAddDel(cols[i].className,"","bw-tab-active");
2986
+ }
2987
+ }
2988
+ //console.log(item);
2989
+ var tcols=[];// = item.parentNode.parentNode.getElementsByClassName("bw-tab-content-list")[0].getElementsByClassName("bw-tab-content");
2990
+
2991
+
2992
+ for (i=0; i<item.parentNode.parentNode.children.length; i++) {
2993
+ if (item.parentNode.parentNode.children[i].className.trim().split(/\s+/).indexOf("bw-tab-content-list")>=0) {
2994
+ //were in the right child...
2995
+ for (j=0; j<item.parentNode.parentNode.children[i].children.length;j++) {
2996
+ if (item.parentNode.parentNode.children[i].children[j].className.trim().split(/\s+/).indexOf("bw-tab-content") >=0)
2997
+ tcols.push(item.parentNode.parentNode.children[i].children[j]);
2998
+ }
2999
+ }
3000
+
3001
+ }
3002
+
3003
+ if (tcols.length <= 0)
3004
+ return false;
3005
+
3006
+ target = (_to(target) == "undefined") ? tcols[index] : target; //we will infer it by the tab index
3007
+ target = (_to(target) == "string" ) ? bw.DOM(target)[0] : target; // we hav an ID so we'll use that
3008
+
3009
+ for (i=0; i < tcols.length; i++) {
3010
+ if (tcols[i] == target)
3011
+ tcols[i].className = bw.classStrAddDel(tcols[i].className,"bw-show");
3012
+ //bw.DOMClass(tcols[i],"bw-show","bw-show"); //tcols[i].style.display = "block";
3013
+ else
3014
+ tcols[i].className = bw.classStrAddDel(tcols[i].className,"","bw-show");
3015
+ //bw.DOMClass(tcols[i],"bw-show","");//tcols[i].style.display = "none";
3016
+
3017
+ }
3018
+ return true;
3019
+ };
3020
+
3021
+ // =============================================================================================
3022
+
3023
+ bw.DOMClass = function(el, key, replace) {
3024
+ /**
3025
+ bw.DOMClass(el,value)
3026
+
3027
+ returns whether a specific DOM element class name (key) is set on atleast one the supplied element(s).
3028
+
3029
+ If replace is supplied then the class name (key) is replaced or added if it doesn't exist.
3030
+ note that if key is not found but a replace is supplied the return-value is still false as the supplied key was not found
3031
+
3032
+ el must be valid element or CSS selector
3033
+
3034
+ markElement is used by bw UI toggles
3035
+ */
3036
+ var r = false, elems, x,j;
3037
+ elems = bw.DOM(el);
3038
+ if (elems.length <=0 )
3039
+ return r;
3040
+
3041
+ for (j=0; j< elems.length; j++) {
3042
+ x = elems[j];
3043
+ try {
3044
+
3045
+ var c = x.className.split(/[ ]+/);
3046
+ var i = c.indexOf(key);
3047
+
3048
+ if (i >= 0) // found key
3049
+ r = true;
3050
+
3051
+
3052
+ if ((_to(replace) == "string") && (c.indexOf(replace)== -1)){
3053
+ if (i == -1) //key not found
3054
+ c.push(replace);
3055
+ else {
3056
+ if (replace.length > 0)
3057
+ c[i]=replace;
3058
+ else
3059
+ c.splice(i,1);
3060
+ }
3061
+ x.className = c.join(" ").trim();
3062
+ r = true;
3063
+ // element.className = element.className.replace(/\bmystyle\b/g, "");
3064
+ }
3065
+ /*
3066
+ var c = x.className;
3067
+ x.className = bw.classStrAddDel(c,key,replace);
3068
+ r=true;
3069
+ */
3070
+ }
3071
+ catch(e) { bw.log(e);}
3072
+ }
3073
+ return r;
3074
+ };
3075
+
3076
+ // =============================================================================================
3077
+ bw.DOMClassToggle = function(el,className) {
3078
+ /**
3079
+ bw.DOMClassToggle(el,classname)
3080
+ for each element specified in el (eg "#id", ".myClass", "h2", <DOM OBJECT>) toggle className.
3081
+
3082
+ If className is present on the object then it is removed. if it is not present it is added.
3083
+
3084
+
3085
+ classNames with spaces or tabs are not valid and result in undefined behavior.
3086
+
3087
+ returns last element current toggle state.
3088
+ */
3089
+ var x,i,elems = bw.DOM(el), r=false;
3090
+ for (i=0; i< elems.length; i++) {
3091
+ x=elems[i];
3092
+ try {
3093
+ r = bw.DOMClass(x,className);
3094
+ if (r)
3095
+ bw.DOMClass(x,className,"");
3096
+ else
3097
+ bw.DOMClass(x,className,className);
3098
+
3099
+ } catch(e) { bw.log(e); }
3100
+ }
3101
+ return !r;
3102
+ };
3103
+ // =============================================================================================
3104
+ bw.version = function() {
3105
+ /**
3106
+ @method version() - bitwrench runtime version & license info.
3107
+
3108
+ */
3109
+ var v = {
3110
+ "version" : "1.2.14",
3111
+ "about" : "bitwrench is a simple library of miscellaneous Javascript helper functions for common web design tasks.",
3112
+ "copy" : "(c) M A Chatterjee deftio (at) deftio (dot) com",
3113
+ "url" : "http://github.com/deftio/bitwrench",
3114
+ "license" : "BSD-2-Clause"
3115
+ };
3116
+ return v;
3117
+ };
3118
+
3119
+ // ==============================================================================================
3120
+ /**
3121
+ command line handling
3122
+
3123
+ this can be done via URL e.g. myPage.com?bw-load-styles=true
3124
+
3125
+ or via script tag
3126
+ <script type="text/javascript" src="./path/to/bitwrenchjs" bwargs="bw-load-styles=true"></script>
3127
+
3128
+ */
3129
+ bw.bwargs = {enableUJURLArgs : "true"}; // the arguments are exported so one can see them as a simple dict
3130
+
3131
+ var parseArgs = function(s) {
3132
+ var args = {};
3133
+ if ((typeof s == "string") && (s!= "")) {
3134
+ s=s.split(";");
3135
+ var j;
3136
+ for (j in s) {
3137
+ var k = s[j].split(":");
3138
+ args[k[0]]=k[1];
3139
+ }
3140
+ }
3141
+ return args;
3142
+ };
3143
+
3144
+ var getArgs = function () {
3145
+ if(bw.isNodeJS()==false) { // in browser
3146
+ //var els = document.getElementsByTagName("script"); // array of script elements
3147
+ var els = bw.DOM("script");
3148
+ var i,a,b;
3149
+ for (i in els) {
3150
+ try {
3151
+ // bw.log(_args[i]);
3152
+ var el = els[i]; //
3153
+ if (el.hasOwnProperty("src") != false)
3154
+ break;
3155
+
3156
+ var s = String(el.getAttribute("src"));
3157
+ var f = "bitwrench.js";
3158
+
3159
+ if (s.toLocaleLowerCase().substring(s.length-f.length,s.length) == f.toLocaleLowerCase()) {
3160
+ s = _to(s) == "string" ? el.getAttribute("bwargs") : [""];
3161
+ s = _to(s) == "string" ? el.getAttribute("data-bwargs") : s; //the html4/5 standard way
3162
+ a = parseArgs(s);
3163
+ for (b in a)
3164
+ bw.bwargs[b]=a[b];
3165
+ }
3166
+
3167
+ } catch (e) {
3168
+ //bw.log(String(["err 1418",i,e]));
3169
+ }
3170
+ }
3171
+
3172
+ //pick up from URL
3173
+ if(bw.bwargs["enableUJURLArgs"] == "true") { //note string literal "true"
3174
+ //note in the script tag you can disable ?bwload=foo:bar; params with this
3175
+ //<script type="text/javascript" src="./path/to/bitwrench.js" bwargs="enableURLConfig:false"></script>
3176
+ a = parseArgs(bw.getURLParam("bwargs",""));
3177
+ for (b in a)
3178
+ bw.bwargs[b]=a[b];
3179
+ }
3180
+ }
3181
+ };
3182
+
3183
+
3184
+ // ==============================================================================================
3185
+ // ==============================================================================================
3186
+ // ==============================================================================================
3187
+ //internally used function declarations:
3188
+
3189
+
3190
+ getArgs();
3191
+
3192
+ // do command line stuff
3193
+ var loadStyles = bw.bwargs["bw-load-styles"]!="false";
3194
+ var loadStyleBasics = bw.typeAssign(bw.bwargs["bw-load-style-basics"],"string", bw.bwargs["bw-load-style-basics"], "load");
3195
+ bw.CSSSimpleStyles(loadStyles,{"globals":loadStyleBasics}); // append to head the bitwrench css styles by default
3196
+
3197
+ bw.funcRegister(bw.log,"bw_log"); // this is globally registered for debugging purposes, it will never get called though unless programmer does this explicitly.
3198
+
3199
+ return bw;
3200
+
3201
+ })();
3202
+ export default bw;