1// Copyright (c) 2016-2026 John Seamons, ZL4VO/KF6VO 2 3/* 4 5 //////////////////////////////// 6 // API summary (w3_*) 7 //////////////////////////////// 8 9 integrated: L=label T=text 3=psa3() 10 | callback 11 | first: F=always false, not called on instan 12 | 13 nav navdef 14 15 label set_label 16 17 link 18 19 radio_button T no label option needed 20 radio_button_get_param T w3-defer 21 radio_unhighlight 22 23 switch T 24 switch_label TL rename w3_switch_label() to w3_switch()? Any external API users? 25 switch_label_get_param w3-defer 26 switch_set_value 27 switch_get_param w3-defer 28 29 button T w3-noactive w3-momentary w3-custom-events w3-disabled 30 button_path 31 button_text 32 icon T w3-momentary 33 spin 34 35 input L3 cb F 36 input_change 37 input_get w3-defer 38 39 textarea L 40 textarea_get_param L w3-defer 41 42 checkbox L3 43 checkbox_get_param L w3-defer 44 checkbox_get 45 checkbox_set 46 47 select L3 48 select_hier L 49 select_get_param L w3-defer 50 select_enum 51 select_value 52 select_set_if_includes 53 54 slider L3 55 slider_set 56 slider_get_param w3-defer 57 58 menu 59 menu_items 60 menu_popup 61 menu_onclick 62 63 inline 3 64 inline_percent 3 65 divs 3 66 col_percent 3 67 68 69 //////////////////////////////// 70 // Useful stuff 71 //////////////////////////////// 72 73 <element attribute="attribute-values ..." inline-style-attribute="properties ..."> 74 "styles" refers to any style-attribute="properties ..." combination 75 76 style properties set one of three ways: 77 1) in an element as above 78 79 2) in a .css: 80 selector 81 { 82 property: property-value; 83 } 84 85 3) assigned by DOM: 86 el.style.property = property-value; 87 88 priority (specificity) 89 90 [inline-style attribute], [id], [class, pseudo-class, attribute], [elements] 91 92 The "count" at each level is determined by the number of specifiers, hence the "specific-ness" 93 e.g. ul#nav li.active a => 0,1,1,3 with 3 due to the ul, li and a 94 This would have priority over 0,1,1,2 95 96 select (menu) behavior: 97 If you set the background-color inline then border reverts to the undesirable beveled appearance! 98 99 in w3.css: 100 w3-show-block 101 w3-show-inline-block 102 103 w3-section: margin TB 16px 104 w3-container: padding TB 0.01em LR 16px 105 w3-row:after: content: ""; display: table; clear: both 106 w3-col: float left, width 100% 107 w3-padding: T 8px, B 16px 108 w3-row-padding: LR 8px 109 w3-margin: TBLR 16px 110 111 in w3_ext.css: 112 w3-show-inline 113 w3-valign 114 w3-override-(colors) 115 116 id="foo" on...(e.g. onclick)=func(this.id) 117 118 119 Be sure to document our special side-effects: 120 w3-label-inline 121 122 123 //////////////////////////////// 124 // Notes about HTML/DOM 125 //////////////////////////////// 126 127 "Typically, the styles are merged, but when conflicts arise, the later declared style will generally win 128 (unless the !important attribute is specified on one of the styles, in which case that wins). 129 Also, styles applied directly to an HTML element take precedence over CSS class styles." 130 131 window. 132 inner{Width,Height} does not include tool/scroll bars 133 outer{Width,Height} includes tool/scroll bars 134 135 element. 136 client{Width,Height} viewable only; no: border, scrollbar, margin; yes: padding 137 offset{Width,Height} viewable only; includes padding, border, scrollbars 138 139 140 //////////////////////////////// 141 // FIXME cleanups 142 //////////////////////////////// 143 144 some routines that return nothing now could return el from internal w3_el() so 145 caller could do chained references (see w3_innerHTML(), w3_show() ...) 146 147 needs uniform instantiation callbacks 148 - w3_nav 149 x w3_navdef 150 >>> w3_radio_button 151 >>> w3_switch 152 - w3_button (only 1 initial state) 153 - w3_icon 154 ??? w3_input(_psa): integrate, 155 - w3_textarea 156 x w3_checkbox 157 x w3_select 158 x w3_slider 159 - w3_menu (no initial selection) 160 161 general conversion to w3-flex layout 162 w3_col_percent => w3_inline_percent 163 kiwi/{admin,admin_sdr}.js: w3_select('' => w3_select(w3-text-red 164 unify color setting: style.color vs w3-color, w3-text-color 165 uniform default/init control values 166 preface internal routines/vars with w3int_... 167 move some routines (esp HTML) out of kiwi_util.js into here? 168 make all 'id-', 'cl-' use uniform 169 collapse into one func the setting of cfg value and el/control current displayed value
170 w3-valign vs w3-show-inline vs w3-show-inline-block 171 172 x use DOM el.classList.f() instead of ops on el.className 173 x normalize use of embedded labels 174 175 176 //////////////////////////////// 177 // API users 178 //////////////////////////////// 179 180 1) kiwisdr.com website content 181 182 2) antenna switch extension is a user of API: 183 w3_div() 184 w3_divs() 185 w3_inline() only in OLDER versions of the ant_switch ext 186 w3_string_set_cfg_cb() 187 w3_highlight() 188 w3_unhighlight() 189 w3_radio_unhighlight() 190 var w3_highlight_time 191 192 ext: 193 ext_{get,set}_cfg_param 194 EXT_SAVE, EXT_NO_SAVE 195 ext_set_controls_width_height 196 ext_send 197 ext_admin_config 198 ext_param 199 ext_panel_show 200 ext_switch_to_client 201 202*/ 203 204 205var w3 = { 206 CAPTURING: true, 207 BUBBLING: false, 208 RETAIN_FOCUS: true, 209 210 _last_: 0 211}; 212 213var w3int = { 214 btn_grp_uniq: 0, 215 alert: {}, 216 alert_seq: 0, 217 218 menu_cur_id: null, 219 menu_active: false, 220 menu_debug: false, 221 menu_close_func: null, 222 prev_menu_hover_el: null, 223 224 reload_timeout: null, 225 reload_seq: 0, 226 227 rate_limit: {}, 228 rate_limit_need_shift: {}, 229 rate_limit_evt: {}, 230 231 eventListener: [], 232 233 _last_: 0 234}; 235 236 237//////////////////////////////// 238// util 239//////////////////////////////// 240 241var w3_console = {}; 242 243// XXX: Made another attempt to get this to work correctly. 244// But is still has lots of corner-case failures compared to "console.log('foo='+ foo +' bar='+ bar)" 245// e.g. wrt undefined, null and object/function values 246w3_console.log = function(obj, prefix) 247{ 248 var s = prefix? (prefix + ': ') : ''; 249 if (isString(obj)) { 250 s += '"'+ obj.toString() +'"'; 251 } else 252 if (isObject(obj)) { 253 console.log(obj); 254 var json = 'JSON '+ JSON.stringify(obj, w3int_console_replacer); 255 if (json.includes('\n')) 256 json = JSON.stringify(obj, w3int_console_replacer, 1); // if json is multi-line, redo using pretty-print 257 s += json; 258 } else { 259 s += obj.toString(); 260 } 261 console.log(s); 262 //if (isObject(obj)) console.log(obj); 263}; 264 265function w3int_console_replacer(k, v) 266{ 267 if (isUndefined(v)) return '<undefined>'; 268 if (isNull(v)) return '<null>'; 269 if (isFunction(v)) return '<function>'; 270 return '<'+ v.toString() +'>'; 271} 272 273function w3_json_to_string_no_decode(id, s) 274{ 275 s = s.replace(/\\"/g, '"'); 276 return s; 277} 278 279function w3_json_to_string(id, s) 280{ 281 s = kiwi_decodeURIComponent(id, s); 282 return w3_json_to_string_no_decode(id, s); 283} 284 285function w3_json_to_html_no_decode(id, s) 286{ 287 //console.log('1> '+ kiwi_string_to_hex(s, '-')); 288 //console.log('1> '+ JSON.stringify(s));
289 s = s.replace(/\\n/g, '<br>').replace(/\n/g, '<br>').replace(/\\"/g, '"'); 290 //console.log('2> '+ JSON.stringify(s)); 291 return s; 292} 293 294function w3_json_to_html(id, s) 295{ 296 s = kiwi_decodeURIComponent(id, s); 297 return w3_json_to_html_no_decode(id, s); 298} 299 300function w3_html_to_nl(s) 301{ 302 return s.replace(/<br>/g, '\n').replace(/\\n/g, '\n'); 303} 304 305function w3_strip_quotes(s) 306{ 307 if (isString(s) && (s.indexOf('\'') != -1 || s.indexOf('\"') != -1)) 308 return s.replace(/'/g, '').replace(/"/g, '') + ' [quotes stripped]'; 309 return s; 310} 311 312function w3_esc_dq(s) 313{ 314 if (isString(s) && s.indexOf('\"') != -1) 315 return s.replace(/"/g, '"'); 316 return s; 317} 318 319// an arguments-in-array call that silently continues if func not found 320function w3_apply(func, args_a) 321{ 322 var rv; 323 324 if (isNoArg(func)) return rv; 325 //console.log('w3_call: '+ kiwi_JSON(args_a)); 326 327 try { 328 if (isString(func)) { 329 var f = getVarFromString(func); 330 //console.log('w3_call: '+ func +'('+ kiwi_JSON(args_a) +')'); 331 if (isFunction(f)) { 332 //var args = Array.prototype.slice.call(arguments); 333 rv = f.apply(null, args_a); 334 } else { 335 //console.error('w3_call: getVarFromString(func) not a function: '+ func +'('+ kiwi_JSON(args_a) +')'); 336 } 337 } else 338 if (isFunction(func)) { 339 rv = func.apply(null, args_a); 340 } else { 341 console.log('w3_call: func not a string or function'); 342 console.log(kiwi_JSON(func)); 343 //kiwi_trace(); 344 } 345 } catch(ex) { 346 console.log('w3_call: while in func this exception occured:'); 347 console.log(ex); 348 //console.log('w3_call: '+ ex.toString()); 349 //console.log(ex.stack); 350 } 351 352 return rv; 353} 354 355// a multi-argument call that silently continues if func not found 356function w3_call(func) 357{ 358 var rv; 359 360 if (isNoArg(func)) return rv; 361 var args_a = Array.from(arguments); // works because arguments is iterable 362 args_a.shift(); // remove func 363 //console.log('w3_call: '+ kiwi_JSON(args_a)); 364 rv = w3_apply(func, args_a); 365 return rv; 366} 367 368function w3_first_value(v) 369{ 370 //console.log('w3_first_value'); 371 //console.log(v); 372 if (v == null) 373 return 'null'; 374 375 var rv = null; 376 var to_v = typeof(v); 377 378 if (to_v === 'number' || to_v === 'boolean' || to_v === 'string') { 379 //console.log('w3_first_value prim'); 380 rv = v; 381 } else 382 if (isArray(v)) { 383 //console.log('w3_first_value array'); 384 rv = v[0]; 385 } else 386 if (to_v === 'object') { // first value in object no matter what the key 387 //console.log('w3_first_value obj'); 388 rv = w3_obj_seq_el(v, 0); 389 } else { 390 //console.log('w3_first_value other'); 391 rv = to_v; 392 } 393 //console.log('w3_first_value rv='+ rv); 394 return rv; 395} 396 397function w3_second_value(v) 398{ 399 //console.log('w3_second_value'); 400 //console.log(v); 401 var rv = null; 402 403 if (isArray(v) && v.length > 1) { 404 //console.log('w3_second_value array[1]'); 405 if (v.length > 1) 406 rv = v[1]; 407 } 408 409 //console.log('w3_second_value rv='+ rv); 410 return rv; 411} 412 413function w3_opt(opt, elem, default_val, pre, post) 414{ 415 //console.log('w3_opt opt:'); 416 //console.log(opt); 417 var s; 418 if (isArg(opt) && isDefined(opt[elem])) { 419 //console.log('w3_opt elem='+ elem +' DEFINED rv='+ opt[elem]); 420 s = opt[elem]; 421 } else { 422 //console.log('w3_opt elem='+ elem +' NOT DEFINED rv='+ default_val); 423 s = default_val; 424 } 425 if (isNonEmptyString(s)) { 426 if (isNonEmptyString(pre)) s = pre + s; 427 if (isNonEmptyString(post)) s = s + post; 428 } 429 return s; 430} 431 432function w3_obj_num(o) 433{ 434 if (isUndefined(o) || isNull(o) || isObject(o)) return o; 435 if (isNumber(o)) return { num: o }; 436 if (isBoolean(o)) return { num: (o? 1:0) }; 437 if (isString(o)) return { num: parseFloat(o) }; 438 return o; 439} 440 441function w3_obj_key_count(obj) { return Object.keys(obj).length; } 442 443// given a sequential position in an object's keys, return the corresponding object element for that key 444function w3_obj_seq_el(obj, idx) 445{ 446 var keys_a = Object.keys(obj); 447 if (!isNumber(idx) || idx < 0 || idx >= keys_a.length) return null; 448 return obj[keys_a[idx]]; 449} 450 451// given an object key, return its sequential position in that object 452function w3_obj_key_seq(obj, key) 453{ 454 var seq = null;
455 Object.keys(obj).forEach(function(_key, i) { 456 if (_key == key) seq = i; 457 }); 458 return seq; 459} 460 461function w3_array_el_seq(arr, el, opt) 462{ 463 var toLower = w3_opt(opt, 'toLower', false); 464 var seq = null; 465 arr.forEach(function(a_el, i) { 466 var ael = toLower? a_el.toLowerCase() : a_el; 467 if (ael == el) seq = i; 468 }); 469 return seq; 470} 471 472function w3_obj_enum(obj, func, opt) 473{ 474 var key_match = w3_opt(opt, 'key_match', undefined); 475 var value_match = w3_opt(opt, 'value_match', undefined); 476 477 Object.keys(obj).forEach(function(key, i) { 478 var o = obj[key]; 479 if (isDefined(key_match)) { 480 if (key == key_match) func(key, i, o); 481 } else 482 if (isDefined(value_match)) { 483 if (o == value_match) func(key, i, o); 484 } else { 485 func(key, i, o); 486 } 487 }); 488} 489 490function w3_enum_obj_or_array_of_objs(obj_arr, func) 491{ 492 if (isArray(obj_arr)) { 493 obj_arr.forEach(function(obj) { 494 func(obj); 495 }); 496 } else 497 498 if (isObject(obj_arr)) { 499 func(obj_arr); 500 } 501} 502 503// merge the object fields of osrc into odst 504function w3_obj_mix(odst, osrc) 505{ 506 if (isArg(osrc)) 507 w3_obj_enum(osrc, function(key, i, o) { 508 odst[key] = o; 509 }); 510 return odst; 511} 512 513// arr: [] string vals to iterate over 514// s: string val to match with startsWith() (case-insensitive) 515// func: (optional) called as func(arr-idx, val) only on first match 516// returns: found = true|false 517function w3_ext_param_array_match_str(arr, s, func) 518{ 519 var found = false; 520 arr.forEach(function(a_el, i) { 521 var el = a_el.toString().toLowerCase(); 522 //console.log('w3_ext_param_array_match_str CONSIDER '+ i +' '+ el +' '+ s); 523 if (!found && el.startsWith(s)) { 524 //console.log('w3_ext_param_array_match_str MATCH '+ i); 525 if (func) func(i, a_el.toString()); 526 found = true; 527 } 528 }); 529 return found; 530} 531 532// arr: [] vals to iterate over 533// n: num val to match 534// func: (optional) called as func(arr-idx, val) only on first match 535// returns: found = true|false 536function w3_ext_param_array_match_num(arr, n, func) 537{ 538 var found = false; 539 arr.forEach(function(a_el, i) { 540 var a_num = parseFloat(a_el); 541 //console.log('w3_ext_param_array_match_num CONSIDER '+ i +' '+ a_num +' '+ n); 542 if (!found && a_num == n) { 543 //console.log('w3_ext_param_array_match_num MATCH '+ i); 544 if (func) func(i, n); 545 found = true; 546 } 547 }); 548 return found; 549} 550 551// s: name.startsWith(s) case-insensitive 552// null wildcard 553// param: name[:val] 554// returns: { match:true|false, full_match:true|false, has_value:true|false, num:parseFloat(val), string:val, string_case:val, items:[] } 555function w3_ext_param(s, param) 556{ 557 var rv = { match:false, full_match:false, has_value:false }; 558 var pu = param.split(':'); 559 var pl = param.toLowerCase().split(':'); 560 //console.log('w3_ext_param: s='+ s +' pu='+ pu[0] +'|'+ pu[1]); 561 if (isNull(s) || s.startsWith(pl[0])) { 562 rv.match = true; 563 if (isNull(s)) { 564 rv.has_value = true; 565 rv.num = parseFloat(param); 566 rv.string = param; 567 rv.string_case = param; 568 rv.items = []; 569 } else 570 if (pl.length > 1) { 571 rv.has_value = true; 572 rv.num = parseFloat(pl[1]); 573 574 // allow string value to contain ':' 575 rv.string = pl.slice(1).join(':'); 576 rv.string_case = pu.slice(1).join(':'); 577 rv.items = pu; 578 } else { 579 rv.num = 0; 580 rv.string = rv.string_case = ''; 581 rv.items = []; 582 } 583 } 584 if (s && s == pl[0]) rv.full_match = true; 585 //console.log(rv); 586 return rv; 587} 588 589function w3_clamp(v, min, max, clamp_val) 590{ 591 if (isNaN(v)) return (isDefined(clamp_val)? clamp_val : NaN); 592 593 if (isObject(min)) { 594 var o = min;
595 try { 596 if (typeof(o.clamp) === 'undefined') { 597 v = Math.max(o.min, Math.min(o.max, v)); 598 } else { 599 if (v < o.min || v > o.max) v = o.clamp; 600 } 601 } catch(ex) { 602 console.log('w3_clamp: bad obj:'); 603 console.log(o); 604 return undefined; 605 } 606 } else { 607 if (clamp_val == undefined) 608 v = Math.max(min, Math.min(max, v)); 609 else 610 if (v < min || v > max) v = clamp_val; 611 } 612 613 return v; 614} 615 616// to conditionally test for out-of-range use as: 617// if (w3_clamp3(v, min, max)) { in-range } else { out-of-range } 618function w3_clamp3(v, min, max, clamp_min, clamp_max, clamp_NaN) 619{ 620 if (isUndefined(clamp_min)) clamp_min = NaN; 621 if (isUndefined(clamp_max)) clamp_max = NaN; 622 if (isUndefined(clamp_NaN)) clamp_NaN = NaN; 623 if (isNaN(v)) return clamp_NaN; 624 if (v < min) return clamp_min; 625 if (v > max) return clamp_max; 626 return v; 627} 628 629function w3_wrap(v, min, max) 630{ 631 if (v < min) v = max; else 632 if (v > max) v = min; 633 return v; 634} 635 636// This works, but clipboard results in double newlines (\n\n) if e.g. multiple <pre> elements inside a <code> 637// But it is useful to visually show select-all selection. 638/* 639function w3_select_all_visual(id) { 640 var div = w3_el(id); 641 if (!div) return; 642 643 // create a range that covers the entire div 644 var range = document.createRange(); 645 range.selectNodeContents(div); 646 647 var selection = window.getSelection(); 648 selection.removeAllRanges(); // clear any existing selection 649 selection.addRange(range); 650} 651*/ 652 653function w3_copy_to_clipboard_el(id, func) { 654 var el = w3_el(id); 655 if (!el) return; 656 var s = ''; 657 w3_iterate_children(el, // only the nodes one level deep 658 function(child, i) { 659 //console.log(child); 660 //console.log('i='+ i +' <'+ child.textContent +'>'); 661 var t = child.textContent; 662 if (isFunction(func)) t = func(t); 663 s += t +'\n'; 664 } 665 ); 666 //console.log('s='+ s); 667 668 w3_copy_to_clipboard(s); 669 //w3_select_all_visual(el); 670 w3_alert('w3_copy_to_clipboard_alert', '', 'copied to clipboard', null, {width:'180px', fadeout:1000}); 671} 672 673function w3_copy_to_clipboard_deep_el(id, func) { 674 var el = w3_el(id); 675 if (!el) return; 676 var s = ''; 677 w3_iterateDeep_children(el, 678 function(child) { 679 s += func(child); 680 } 681 ); 682 s += '\n'; 683 //console.log('s='+ s); 684 685 w3_copy_to_clipboard(s); 686 //w3_select_all_visual(el); 687 w3_alert('w3_copy_to_clipboard_deep_alert', '', 'copied to clipboard', null, {width:'180px', fadeout:1000}); 688} 689 690function w3_copy_to_clipboard(s) 691{ 692 // this isn't available on non-https connections 693 //navigator.clipboard.writeText(s.trim()); 694 695 // workaround for http connections using execCommand('copy') 696 // NB: This tiny, invisible, readonly textarea element prevents Firefox from doing an inward autozoom. 697 // Also, it is necessary to use textarea and not an input element to preserve newlines. 698 var el = document.createElement("textarea"); 699 el.value = s; 700 el.style.position = "fixed"; 701 el.style.left = "-99999px"; 702 el.style.top = "-99999px"; 703 el.style.width = "1px"; 704 el.style.height = "1px"; 705 el.style.padding = "0"; 706 el.style.border = "0"; 707 el.style.opacity = "0"; 708 el.style.zIndex = "-9999"; 709 el.setAttribute("readonly", "readonly"); 710 document.body.appendChild(el); 711 el.focus(); 712 el.select(); 713 document.execCommand('copy'); 714 document.body.removeChild(el); 715} 716 717//////////////////////////////// 718// HTML 719//////////////////////////////// 720 721function w3_add_id(path, suffix) 722{ 723 if (!isString(path)) return path; 724 path = path || ''; 725 if (path == '' || path == 'body') return path; 726 path = path.startsWith('id-')? path : ('id-'+ path); 727 suffix = suffix || ''; 728 return path + suffix; 729} 730 731// return document element reference either by id or name 732function w3int_w3_el(id_name_class) 733{ 734 if (!id_name_class || id_name_class == '') return null; 735 var el = document.getElementById(id_name_class); 736 if (el == null) { 737 el = document.getElementsByName(id_name_class); // 'name=' is deprecated 738 if (el != null) el = el[0]; // use first from array 739 } 740 if (el == null) { 741 el = document.getElementsByClassName(id_name_class); 742 if (el != null) el = el[0]; // use first from array 743 } 744 return el; 745} 746 747function w3int_w3_els(id_name_class) 748{ 749 if (!id_name_class || id_name_class == '') return null; 750 var els = document.getElementById(id_name_class); 751 var rv; 752 if (els != null) { 753 rv = [els]; 754 } else { 755 els = document.getElementsByName(id_name_class); // 'name=' is deprecated 756 if (els == null || els.length == 0) { 757 els = document.getElementsByClassName(id_name_class); 758 } 759 rv = els; 760 } 761 if (rv.length == 0) rv = null; 762 return rv; // returns single-element array or iterable HTMLCollection 763} 764 765// allow an element-obj or element-id to be used 766// try id without, then with, leading 'id-'; then including cfg prefix as a last resort 767function w3_el(el_id) 768{ 769 if (isString(el_id)) { 770 if (el_id == '') return null; 771 if (el_id == 'body') return document.body; 772 var el = w3int_w3_el(el_id); 773 if (el == null) { 774 el = w3int_w3_el(w3_add_id(el_id)); 775 if (el == null) { 776 el_id = w3_add_toplevel(el_id); 777 el = w3int_w3_el(el_id); 778 if (el == null) { 779 el = w3int_w3_el(w3_add_id(el_id)); 780 } 781 } 782 } 783 return el; 784 } 785 return (el_id); 786} 787 788// for when there are multiple elements with the same "id-*" 789// returns element list and optionally calls iterates calling func() 790function w3_els(el_id, func) 791{ 792 if (isString(el_id)) { 793 if (el_id == '') return null; 794 if (el_id == 'body') return document.body; 795 var els = w3int_w3_els(el_id); 796 if (els == null) { 797 els = w3int_w3_els(w3_add_id(el_id)); 798 if (els == null) { 799 el_id = w3_add_toplevel(el_id); 800 els = w3int_w3_els(el_id); 801 if (els == null) { 802 els = w3int_w3_els(w3_add_id(el_id)); 803 } 804 } 805 } 806 if (els && func) for (var i = 0; i < els.length; i++) { 807 func(els[i], i); 808 } 809 return els; 810 } 811 812 var el = w3_el(el_id); 813 if (func) { 814 func(el, 0); 815 } 816 return [el]; 817} 818 819// return id of element (i.e. 'id-*') if found 820function w3_id(el_id) 821{ 822 var el = w3_el(el_id); 823 if (!el) return 'id-NULL'; 824 if (el.id && el.id.startsWith('id-')) return el.id; 825 var done = false;
826 var id = null; 827 w3_iterate_classList(el, function(className, idx) { 828 //console.log('w3_id CONSIDER '+ className); 829 if (className.startsWith('id-') && !done) { 830 id = className; 831 done = true; 832 } 833 }); 834 return (id? id : 'id-NULL'); 835} 836 837function w3_activeElement() 838{ 839 // returns 'id-NULL' if activeElement is null 840 return w3_id(document.activeElement); 841} 842 843function w3_noElHasFocus() 844{ 845 var ae = w3_activeElement(); 846 //console.log('ae='+ ae); 847 return (ae == 'id-kiwi-body'); 848} 849 850// assign innerHTML, silently failing if element doesn't exist 851function w3_innerHTML(id) 852{ 853 //var el = w3_el_softfail(id); 854 var el = w3_el(id); 855 if (!el) return null; 856 857 // get 858 if (arguments.length == 1) 859 return el.innerHTML; 860 861 // set 862 var s = ''; 863 var narg = arguments.length; 864 for (var i=1; i < narg; i++) { 865 s += arguments[i]; 866 } 867 el.innerHTML = s; 868 return el; 869} 870 871function w3_clearInnerHTML(id) 872{ 873 w3_innerHTML(id, ''); 874} 875 876function w3_append_innerHTML(id) 877{ 878 var el = w3_el(id); 879 if (!el) return null; 880 var s = ''; 881 var narg = arguments.length; 882 for (var i=1; i < narg; i++) { 883 s += arguments[i]; 884 } 885 el.innerHTML += s; 886 return el; 887} 888 889function w3_get_innerHTML(id) 890{ 891 var el = w3_el(id); 892 if (!el) return null; 893 return el.innerHTML; 894} 895 896function w3_iterate_classname(cname, func) 897{ 898 var els = document.getElementsByClassName(w3_add_id(cname)); 899 if (els == null) return; 900 for (var i=0; i < els.length; i++) { // els is a collection, can't use forEach() 901 func(els[i], i); 902 } 903} 904 905function w3_iterate_classList(el_id, func) 906{ 907 var el = w3_el(el_id); 908 if (el && el.classList) for (var i = 0; i < el.classList.length; i++) { // el.classList is a collection, can't use forEach() 909 func(el.classList.item(i), i); 910 } 911 return el; 912} 913 914function w3_create_appendElement(el_parent, el_type, html, id, psa) 915{ 916 var el_child = document.createElement(el_type); 917 if (isNonEmptyString(id)) el_child.id = id; 918 if (isNonEmptyString(psa)) w3_set_psa(el_child, psa); 919 if (isNonEmptyString(html)) w3_innerHTML(el_child, html); 920 w3_el(el_parent).appendChild(el_child); 921 return el_child; 922} 923 924/* 925not currently used 926// NB: only elements, not text and comments nodes 927function w3_trim_childElements(el_parent, max_len) 928{ 929 var el = w3_el(el_parent); 930 while (el.children.length > max_len) 931 el.removeChild(el.firstElementChild); 932} 933*/ 934 935function w3_iterate_parent(el_id, func) 936{ 937 var el = w3_el(el_id); 938 var i = 0; 939 940 do { 941 if (func(el, i) != null) 942 break; 943 el = el.parentNode; 944 i++; 945 } while (el); 946} 947 948// excludes text and comment nodes 949function w3_iterate_children(el_id, func) 950{ 951 var el = w3_el(el_id); 952 if (!el) return null; 953 954 for (var i=0; i < el.children.length; i++) { // el.children is a collection, can't use forEach() 955 var child_el = el.children[i]; 956 func(child_el, i); 957 } 958} 959 960// excludes text and comment nodes 961function w3_iterateDeep_children(el_id, func, level) 962{ 963 var el = w3_el(el_id); 964 if (!el) return null; 965 if (isUndefined(level)) level = 0; 966 967 for (var i=0; i < el.children.length; i++) { // el.children is a collection, can't use forEach() 968 var child_el = el.children[i]; 969 func(child_el, level, i); 970 if (child_el.hasChildNodes) 971 w3_iterateDeep_children(child_el, func, level+1); 972 } 973} 974 975// includes text and comment nodes 976function w3_iterate_childNodes(el_id, func) 977{ 978 var el = w3_el(el_id); 979 980 for (var i=0; i < el.childNodes.length; i++) { // el.childNodes is a collection, can't use forEach() 981 var child_el = el.childNodes[i]; 982 func(child_el, i); 983 } 984} 985 986function w3_width_height(el_id, w, h) 987{ 988 var el = w3_el(el_id); 989 if (!el) return null; 990 991 if (isArg(w)) { 992 if (isNumber(w)) w = px(w); 993 el.style.width = w; 994 } 995 996 if (isArg(h)) { 997 if (isNumber(h)) h = px(h); 998 el.style.height = h; 999 } 1000 1001 w = el.style.width; 1002 h = el.style.height; 1003 return { w:parseInt(w), width:w, h:parseInt(h), height:h }; 1004} 1005 1006function w3_border(el_id, s) 1007{ 1008 var el = w3_el(el_id); 1009 if (!el) return null; 1010 el.style.border = s; 1011 return el; 1012} 1013 1014// bounding box measured from the origin of parent 1015function w3_boundingBox_children(el_id, debug) 1016{ 1017 var el = w3_el(el_id); 1018 var bbox = { id:el_id, x1:1e99, x2:0, y1:1e99, y2:0, w:0, h:0 }; 1019 var found = false;
1020 w3_iterateDeep_children(el_id, function(el, level, i) { 1021 if (el.nodeName != 'DIV' && el.nodeName != 'SPAN' && el.nodeName != 'IMG') 1022 return; 1023 var position = css_style(el, 'position'); 1024 if (position == 'static') 1025 return; 1026 if (el.offsetHeight == 0) 1027 return; 1028 if (debug) console.log('level='+ level +' i='+ i); 1029 if (debug) console.log(el); 1030 if (!found) { bbox.el = el; found = true; } 1031 //console.log(el.offsetParent); 1032 if (debug) console.log(el.nodeName +' el.oL='+ el.offsetLeft +' el.oW='+ el.offsetWidth +' el.oT='+ el.offsetTop +' el.oH='+ el.offsetHeight +' '+ position); 1033 1034 bbox.x1 = Math.min(bbox.x1, el.offsetLeft); 1035 var x2 = el.offsetLeft + el.offsetWidth; 1036 bbox.x2 = Math.max(bbox.x2, x2); 1037 1038 bbox.y1 = Math.min(bbox.y1, el.offsetTop); 1039 var y2 = el.offsetTop + el.offsetHeight; 1040 bbox.y2 = Math.max(bbox.y2, y2); 1041 }); 1042 1043 bbox.w = bbox.x2 - bbox.x1; 1044 bbox.h = bbox.y2 - bbox.y1; 1045 if (bbox.w == -1e+99) { bbox.w = bbox.h = 0; } 1046 //if (debug) w3_console.log(bbox, 'w3_boundingBox_children'); 1047 if (debug) console.log(bbox); 1048 return bbox; 1049} 1050 1051function w3_center_in_window(el_id, id) 1052{ 1053 var el = w3_el(el_id); 1054 var rv = (window.innerHeight - el.clientHeight) / 2; 1055 //console.log('w3_center_in_window wh='+ window.innerHeight +' ch='+ el.clientHeight +' rv='+ rv + (id? (' '+ id) : '')); 1056 return rv; 1057} 1058 1059// conditionally select field element 1060// also used to dismiss popup keyboard on mobile via el.blur() 1061function w3_field_select(el_id, opts) 1062{ 1063 var el = w3_el(el_id); 1064 el = (el && isFunction(el.select))? el : null; 1065 1066 opts = opts || {}; 1067 var trace = opts.trace; 1068 if (trace) { 1069 var id = isObject(el_id)? el_id.id : el_id; 1070 console.log('w3_field_select id='+ id +' el='+ el +' v='+ (el? el.value:null)); 1071 console.log(el); 1072 console.log(opts); 1073 } 1074 if (!el) return; 1075 1076 // If ext_set_scanning() has set extint.scanning then don't perform a field select 1077 // if this element specifies the "scanning:1" option. 1078 // 1079 // The ALE_2G extension currently does this so its control panel UI items can be used 1080 // without being interrupted by the frequency entry field taking focus away during 1081 // each scan frequency tune. 1082 if (opts.scanning && extint.scanning) return; 1083 1084 var focus=0, select=0, blur=0, dismiss=0; 1085 if (opts.mobile) { 1086 if (kiwi_isMobile()) blur = 1; else focus = select = 1; 1087 } 1088 if (opts.blur) blur = 1; 1089 if (opts.dismiss_kybd && kiwi_isMobile()) dismiss = 1; 1090 if (opts.focus_select) focus = select = 1; 1091 if (opts.focus_only) { focus = 1; select = 0; } 1092 if (opts.select_only) select = 1; 1093 1094 if (trace) console.log('w3_field_select focus='+ focus +' select='+ select +' blur='+ blur +' dismiss='+ dismiss); 1095 //if (opts.log) canvas_log('$'+ opts.log +': F='+ focus +' S='+ select +' B='+ blur +' D='+ dismiss +' S='+ TF(kiwi_isOld_iOS())); 1096 1097 // old iPad-2 safari/iOS does this weird repeating keyboard popup if keyboard blurred this way 1098 if (kiwi_isOld_iOS()) dismiss = 0; 1099 1100 if (focus) el.focus(); 1101 if (select) el.select(); 1102 if (blur) el.blur(); 1103 if (dismiss) el.blur(); 1104 if (trace) kiwi_trace(); 1105} 1106 1107function w3_dismiss_keyboard(el) 1108{ 1109 w3_field_select(el, {dismiss_kybd:1}); 1110} 1111 1112// add, remove or check presence of class properties 1113function w3_add(el_id, props) 1114{ 1115 if (!el_id || isEmptyString(props)) return null; 1116 1117 var first_el = null; 1118 // FIXME: why doesn't this work?!? 1119 //w3_els(el_id, function(el) { 1120 var el = w3_el(el_id); 1121 if (!el) return null; 1122 if (first_el) return; 1123 //console.log('$w3_add '+ el_id +' <'+ props +'>'); 1124 if (!el || !props) return null; 1125 props = props.replace(/\s+/g, ' ').split(' '); 1126 props.forEach(function(p) { 1127 el.classList.add(p); 1128 }); 1129 if (!first_el) first_el = el; 1130 //}); 1131 1132 return first_el; 1133 //return el; 1134} 1135 1136// remember selector syntax: #id .class 1137function w3_add_by_selector(selector, props) 1138{ 1139 var node_list = document.querySelectorAll(selector); 1140 if (isNull(node_list)) return; 1141 node_list.forEach( 1142 function(el,i) { 1143 //console.log(i +' '+ el.id); 1144 w3_add(el, props); 1145 } 1146 ); 1147} 1148 1149function w3_remove(el_id, props) 1150{ 1151 if (!el_id || isEmptyString(props)) return null; 1152 1153 var first_el = null; 1154 //w3_els(el_id, function(el) { 1155 var el = w3_el(el_id); 1156 if (!el) return null; 1157 if (!first_el) first_el = el; 1158 //console.log('$w3_remove <'+ props +'>'); 1159 if (!el || !props) return null; 1160 props = props.replace(/\s+/g, ' ').split(' '); 1161 props.forEach(function(p) { 1162 el.classList.remove(p); 1163 }); 1164 //}); 1165 1166 return first_el; 1167} 1168 1169// remember selector syntax: #id .class 1170function w3_remove_by_selector(selector, props) 1171{ 1172 var node_list = document.querySelectorAll(selector); 1173 if (isNull(node_list)) return; 1174 node_list.forEach( 1175 function(el,i) { 1176 //console.log(i +' '+ el.id); 1177 w3_remove(el, props); 1178 } 1179 ); 1180} 1181 1182function w3_match_wildcard(el_id, prefix) 1183{ 1184 var el = w3_el(el_id); 1185 //console.log('w3_match_wildcard <'+ prefix +'>'); 1186 if (!el) return null; 1187 for (var i = 0; i < el.classList.length; i++) { // el.classList is a collection, can't use forEach() 1188 var prop = el.classList.item(i); 1189 //console.log('w3_match_wildcard CONSIDER <'+ prop +'>'); 1190 if (prop.startsWith(prefix)) { 1191 var a = prop.split(prefix); 1192 //console.log(a); 1193 var suffix = (a.length >= 2)? a[1] : ''; 1194 return { prop:prop, suffix:suffix, num:suffix.filterInt() }; 1195 } 1196 } 1197 return false;
1198} 1199 1200function w3_remove_wildcard(el_id, prefix) 1201{ 1202 var el = w3_el(el_id); 1203 //console.log('w3_remove_wildcard <'+ prefix +'>'); 1204 if (!el) return null; 1205 for (var i = 0; i < el.classList.length; i++) { // el.classList is a collection, can't use forEach() 1206 var prop = el.classList.item(i); 1207 if (prop.startsWith(prefix)) el.classList.remove(prop); 1208 } 1209 return el; 1210} 1211 1212function w3_set_props(el_id, props, cond) 1213{ 1214 var el = w3_el(el_id); 1215 if (!el) return el; 1216 cond = isArg(cond)? cond : true; 1217 1218 if (cond) 1219 w3_add(el, props); 1220 else 1221 w3_remove(el, props); 1222 return el; 1223} 1224 1225function w3_remove_then_add(el_id, r_props, a_props) 1226{ 1227 w3_remove(el_id, r_props); 1228 w3_add(el_id, a_props); 1229} 1230 1231function w3_remove_then_add_cond(el_id, cond, t_props, f_props) 1232{ 1233 w3_remove(el_id, t_props +' '+ f_props); 1234 w3_add(el_id, cond? t_props : f_props); 1235} 1236 1237// remember selector syntax: #id .class 1238function w3_remove_then_add_cond_by_selector(selector, cond, t_props, f_props) 1239{ 1240 var node_list = document.querySelectorAll(selector); 1241 if (isNull(node_list)) return; 1242 node_list.forEach( 1243 function(el,i) { 1244 //console.log(i +' '+ el.id); 1245 w3_remove_then_add_cond(el, cond, t_props, f_props); 1246 } 1247 ); 1248} 1249 1250// remember selector syntax: #id .class 1251function w3_nodes_by_selector(selector) 1252{ 1253 return document.querySelectorAll(selector); 1254} 1255 1256// remember selector syntax: #id .class 1257function w3_func_by_selector(selector, func) 1258{ 1259 var args = Array.from(arguments); // works because arguments is iterable 1260 args.shift(); args.shift(); // remove selector, func 1261 console.log(args); 1262 var node_list = document.querySelectorAll(selector); 1263 console.log(node_list); 1264 if (isNull(node_list)) return null; 1265 node_list.forEach( 1266 function(el,i) { 1267 console.log(i +' id='+ el.id); 1268 var a = args; 1269 a.unshift(el); // add el 1270 console.log(a); 1271 w3_apply(func, a); 1272 } 1273 ); 1274} 1275 1276function w3_contains(el_id, prop) 1277{ 1278 var el = w3_el(el_id); 1279 if (!el) return 0; 1280 var clist = el.classList; 1281 return (!clist || !clist.contains(prop))? 0:1; 1282} 1283 1284function w3_parent_with(el_id, prop) 1285{ 1286 var el = w3_el(el_id); 1287 if (!el) return; 1288 1289 var found = null; 1290 w3_iterate_parent(el, function(parent) { 1291 if (!found && w3_contains(parent, prop)) { 1292 found = parent; 1293 } 1294 }); 1295 return found; 1296} 1297 1298function w3_toggle(el_id, prop) 1299{ 1300 var el = w3_el(el_id); 1301 if (!el) return; 1302 if (w3_contains(el, prop)) { 1303 w3_remove(el, prop); 1304 //console.log('w3_toggle: hiding '+ el_id); 1305 } else { 1306 w3_add(el, prop); 1307 //console.log('w3_toggle: showing '+ el_id); 1308 } 1309 return el; 1310} 1311 1312function w3_appendAllClass(cname, prop) 1313{ 1314 w3_iterate_classname(cname, function(el) { el.classList.add(prop); }); 1315} 1316 1317function w3_setAllHref(cname, href) 1318{ 1319 w3_iterate_classname(cname, function(el) { el.href = href; }); 1320} 1321 1322// Can't simply do "el.style.display =" for these since things like 1323// w3-hide are declared "!important" by w3.css 1324// Must actually remove/insert them from the class property. 1325function w3_show(el_id, display) 1326{ 1327 var el = w3_el(el_id); 1328 if (!el) return el; 1329 1330 w3_remove(el, 'w3-hide'); 1331 w3_add(el, display? display : 'w3-show-inline-block'); 1332 return el; 1333} 1334 1335function w3_create_attribute(el_id, name, val) 1336{ 1337 var el = w3_el(el_id); 1338 if (el == null) return; 1339 var attr = document.createAttribute(name); 1340 attr.value = val; 1341 el.setAttributeNode(attr); 1342} 1343 1344function w3_attribute(el_id, name, val, cond) 1345{ 1346 var el = w3_el(el_id); 1347 if (el == null) return null; 1348 if (isUndefined(cond) || cond == true) 1349 el.setAttribute(name, val); // repeated sets only update (i.e. don't create duplicate attrs) 1350 else 1351 el.removeAttribute(name); 1352} 1353 1354// e.g. 'title="..." onclick="..."' 1355function w3_set_attributes_from_string(element, s) { 1356 var attrs = s.match(/(\S+)=["']([^"']*)["']/g) || []; 1357 1358 attrs.forEach( 1359 function (attr) { 1360 var [key, value] = attr.split('='); 1361 var cleanKey = key.trim(); 1362 var cleanValue = value.replace(/["']/g, '').trim(); 1363 element.setAttribute(cleanKey, cleanValue); 1364 }); 1365} 1366 1367function w3_show_block(el_id) 1368{ 1369 return w3_show(el_id, 'w3-show-block'); 1370} 1371 1372function w3_show_inline_block(el_id) 1373{ 1374 return w3_show(el_id, 'w3-show-inline-block'); 1375} 1376 1377function w3_show_inline(el_id) 1378{ 1379 return w3_show(el_id, 'w3-inline-flex'); 1380} 1381 1382function w3_show_table_cell(el_id) 1383{ 1384 return w3_show(el_id, 'w3-show-table-cell'); 1385} 1386 1387function w3_hide(el_id) 1388{ 1389 var el = w3_el(el_id); 1390 if (!el) return el; 1391 1392 //w3_console.log(el, 'w3_hide BEGIN'); 1393 el = w3_iterate_classList(el, function(className, idx) { 1394 //console.log('w3_hide CONSIDER '+ className); 1395 if (className.startsWith('w3-show-')) { 1396 //console.log('w3_hide REMOVE '+ className); 1397 w3_remove(el, className); 1398 } 1399 }); 1400 w3_add(el, 'w3-hide'); 1401 //w3_console.log(el, 'w3_hide END'); 1402 return el; 1403} 1404 1405// if overrides are done correctly shouldn't need to remove any w3-show-* 1406// only toggle presence of w3-hide 1407function w3_hide2(el_id, cond) 1408{ 1409 var el = w3_el(el_id); 1410 if (!el) return el; 1411 cond = isArg(cond)? cond : true; 1412 1413 w3_set_props(el, 'w3-hide', cond); 1414 return el; 1415} 1416 1417function w3_show_hide(el_id, show, display, n_parents_up, props, cond) 1418{ 1419 var rv; 1420 var el = w3_el(el_id); 1421 //w3_console.log(el, 'w3_show_hide BEGIN show='+ show); 1422 1423 if (isNumber(n_parents_up)) 1424 for (var i = 0; i < n_parents_up; i++) el = el.parentElement; 1425 1426 if (show) { 1427 rv = w3_show(el, display? display : 'w3-show-block'); 1428 } else { 1429 rv = w3_hide(el); 1430 } 1431 if (isString(props)) w3_set_props(el, props, cond); 1432 //w3_console.log(el, 'w3_show_hide END'); 1433 return rv; 1434} 1435 1436function w3_show_hide_inline(el, show, n_parents_up, props, cond) 1437{ 1438 w3_show_hide(el, show, 'w3-inline-flex', n_parents_up, props, cond); 1439} 1440 1441function w3_disable(el_id, disable, prop) 1442{ 1443 if (!isArg(disable)) disable = true; 1444 1445 el = w3_el(el_id); 1446 if (!el) return null; 1447 //console.log('w3_disable disable='+ disable +' t/o(el)='+ typeof(el) +' nodeName='+ el.nodeName +' type='+ el.type); 1448 //console.log(el); 1449 var input = (el.nodeName == 'INPUT'); 1450 var slider = (input && el.type == 'range'); 1451 var field = (input && el.type == 'text'); 1452 if (!isString(prop)) prop = slider? 'w3-disabled' : 'w3-disabled-field'; 1453 w3_set_props(el, prop, disable); 1454 1455 // for disabling menu popup and sliders 1456 var attr = field? 'readonly' : 'disabled';
1457 if (isDefined(el.nodeName) && (el.nodeName == 'SELECT' || input)) { 1458 try { 1459 if (disable) 1460 el.setAttribute(attr, ''); 1461 else 1462 el.removeAttribute(attr); 1463 } catch(ex) { 1464 console.log('w3_disable:Attribute'); 1465 console.log(ex); 1466 } 1467 } 1468 1469 return el; 1470} 1471 1472function w3_disable_multi(el_id, disable, prop) 1473{ 1474 w3_els(el_id, 1475 function(el, i) { 1476 w3_disable(el, disable, prop); 1477 } 1478 ); 1479} 1480 1481function w3_visible(el_id, visible) 1482{ 1483 if (!isArg(visible)) visible = true; 1484 1485 var el = w3_el(el_id); 1486 if (!el) return null; 1487 el.style.visibility = visible? 'visible' : 'hidden'; 1488 return el; 1489} 1490 1491function w3_isVisible(el_id) 1492{ 1493 var el = w3_el(el_id); 1494 if (!el) return null; 1495 return (el.style.visibility == 'visible'); 1496} 1497 1498function w3_zIndex(el_id, znew) 1499{ 1500 var el = w3_el(el_id); 1501 if (!el) return null; 1502 var zold = el.style.zIndex; 1503 if (isArg(znew)) el.style.zIndex = znew; 1504 return zold; 1505} 1506 1507// our standard for confirming (highlighting) a control action (e.g.button push) 1508var w3_highlight_time = 250; 1509var w3_highlight_color = 'w3-selection-green'; 1510var w3_selection_green = '#4CAF50'; 1511 1512function w3_highlight(el_id) 1513{ 1514 var el = w3_el(el_id); 1515 //console.log('w3_highlight '+ el.id); 1516 w3_add(el, el.w3int_highlight_color || w3_highlight_color); 1517} 1518 1519function w3_unhighlight(el_id) 1520{ 1521 var el = w3_el(el_id); 1522 //console.log('w3_unhighlight '+ el.id); 1523 w3_remove(el, el.w3int_highlight_color || w3_highlight_color); 1524} 1525 1526function w3_isHighlighted(el_id) 1527{ 1528 var el = w3_el(el_id); 1529 return w3_contains(el, el.w3int_highlight_color || w3_highlight_color); 1530} 1531 1532function w3_schedule_highlight(el_id) 1533{ 1534 var el = w3_el(el_id); 1535 w3_highlight(el); 1536 setTimeout(function() { w3_unhighlight(el); }, w3_highlight_time); 1537} 1538 1539function w3_set_highlight_color(el_id, color) 1540{ 1541 var el = w3_el(el_id); 1542 if (!el) return null; 1543 1544 if (w3_isHighlighted(el)) { 1545 w3_unhighlight(el); 1546 el.w3int_highlight_color = color; 1547 w3_highlight(el); 1548 } else { 1549 el.w3int_highlight_color = color; 1550 } 1551 1552 return el; 1553} 1554 1555var w3_flag_color = 'w3-override-yellow'; 1556 1557function w3_flag(path, s) 1558{ 1559 var el = w3_el(path); 1560 if (isString(s)) w3_innerHTML(el, s); 1561 w3_add(el, w3_flag_color); 1562} 1563 1564function w3_unflag(path, s) 1565{ 1566 var el = w3_el(path); 1567 if (isString(s)) w3_innerHTML(el, s); 1568 w3_remove(el, w3_flag_color); 1569} 1570 1571function w3_flag_cond(path, cond, s) 1572{ 1573 var el = w3_el(path); 1574 if (cond) 1575 w3_flag(el, s); 1576 else 1577 w3_unflag(el, s); 1578} 1579 1580// for when you don't want to w3_add(el_id, "[w3-text-color]") 1581// returns previous color 1582function w3_color(el_id, color, bkgColor, cond) 1583{ 1584 var el = w3_el(el_id); 1585 if (!el) return null; 1586 var prev_fg = el.style.color; 1587 var prev_bg = el.style.backgroundColor; 1588 1589 // allow w3_color(el_id, [color, bkgColor], cond) 1590 if (isArray(color)) { 1591 var a = color; 1592 color = a[0]; 1593 cond = bkgColor; 1594 bkgColor = a[1]; 1595 } 1596 1597 // remember that setting colors to '' restores default 1598 cond = (isUndefined(cond) || cond); 1599 if (isArg(color)) el.style.color = cond? color:''; 1600 if (isArg(bkgColor)) el.style.backgroundColor = cond? bkgColor:''; 1601 return { color: prev_fg, backgroundColor: prev_bg }; 1602} 1603 1604// returns previous color 1605function w3_background_color(el_id, color) 1606{ 1607 var el = w3_el(el_id); 1608 var prev = el.style.backgroundColor; 1609 if (color != undefined && color != null) el.style.backgroundColor = color; 1610 return prev; 1611} 1612 1613// c1 = color if cond false 1614// c2 = color (optional) if cond true/undefined 1615// c[12] = 1616// 'css-color(fg)' 1617// ['css-color(fg)', 'css-color(bg)'] 1618// 'w3-text-color'(fg) or 'w3-color'(bg) 1619// CSS color '' to revert to default color 1620function w3_colors(el_id, c1, c2, cond) 1621{ 1622 var el = w3_el(el_id); 1623 if (!el) return null; 1624 cond = isArg(cond)? cond : true; 1625 1626 var fg1 = null, bg1 = null; 1627 if (isArray(c1)) { 1628 if (c1.length > 0) fg1 = c1[0]; 1629 if (c1.length > 1) bg1 = c1[1]; 1630 } else 1631 if (isString(c1)) { 1632 fg1 = c1; 1633 } else { 1634 return null; 1635 } 1636 1637 //console.log('w3_colors fg1='+ fg1 +' '+ cond); 1638 if (fg1.startsWith('w3-')) { 1639 w3_remove(el, fg1); 1640 if (!cond) w3_add(el, fg1); 1641 } else { 1642 if (!cond) el.style.color = fg1; 1643 } 1644 if (bg1) { 1645 el.style.backgroundColor = bg1; 1646 } 1647 1648 var fg2 = null, bg2 = null; 1649 if (isArray(c2)) { 1650 if (c2.length > 0) fg2 = c2[0]; 1651 if (c2.length > 1) bg2 = c2[1]; 1652 } else 1653 if (isString(c2)) { 1654 fg2 = c2; 1655 } else { 1656 return null; 1657 } 1658 1659 //console.log('w3_colors fg2='+ fg2 +' '+ cond); 1660 if (fg2.startsWith('w3-')) { 1661 w3_remove(el, fg2); 1662 if (cond) w3_add(el, fg2); 1663 } else { 1664 if (cond) el.style.color = fg2; 1665 } 1666 if (bg2) { 1667 el.style.backgroundColor = bg2; 1668 } 1669} 1670 1671function w3_flip_colors(el_id, colors, cond) 1672{ 1673 var el = w3_el(el_id); 1674 if (!el) return null; 1675 var ar = colors? colors.split(' ') : null; 1676 if (!ar || ar.length != 2) return null; 1677 w3_remove(el, ar[0]); 1678 w3_remove(el, ar[1]); 1679 w3_add(el, ar[cond? 1:0]); 1680} 1681 1682function w3_fg_color_with_opacity_against_bk_color(fg, opacity, bg) 1683{ 1684 var fg_color = w3color(fg); 1685 if (!fg_color.valid) return null; 1686 var bg_color = w3color(bg); 1687 if (!bg_color.valid) return null; 1688 var _opacity = 1 - opacity; 1689 var red = Math.round(opacity * fg_color.red + _opacity * bg_color.red); 1690 var green = Math.round(opacity * fg_color.green + _opacity * bg_color.green); 1691 var blue = Math.round(opacity * fg_color.blue + _opacity * bg_color.blue); 1692 return w3color(kiwi_rgb(red, green, blue)); 1693} 1694 1695function w3_opacity(el_id, opacity) 1696{ 1697 var el = w3_el(el_id); 1698 if (!el) return null; 1699 el.style.opacity = opacity; 1700} 1701 1702function w3_flash_fade(el_id, color, dwell_ms, fade_ms, color2) 1703{ 1704 var el = w3_el(el_id); 1705 if (!el) return el; 1706 el.style.transition = 'background 0s'; // has to be "0s" and not just "0" 1707 el.style.background = color; 1708 setTimeout(function() { 1709 el.style.transition = 'background '+ (fade_ms? (fade_ms/1000) : 0.3) +'s linear'; 1710 el.style.background = isString(color2)? color2 : 'black'; 1711 }, dwell_ms? dwell_ms : 50); 1712 return el; 1713} 1714 1715function w3_fillText_shadow(canvas, text, x, y, font, fontSize, color, opts) { 1716 var strokeWidth = w3_opt(opts, 'stroke', Math.floor(fontSize / 3)); // rule of thumb for good looking shadow 1717 1718 var ctx = canvas.getContext("2d"); 1719 ctx.font = px(fontSize) +' '+ font; 1720 ctx.miterLimit = 2; 1721 ctx.lineJoin = "circle"; 1722 var tw = ctx.measureText(text).width;
1723 if (!w3_opt(opts, 'left')) x -= tw/2; // i.e. centered 1724 1725 ctx.strokeStyle = "black"; 1726 ctx.lineWidth = strokeWidth; 1727 ctx.strokeText(text, x, y); 1728 1729 ctx.fillStyle = color; 1730 ctx.lineWidth = 1; 1731 ctx.fillText(text, x, y); 1732} 1733 1734function w3_fillText(ctx, x, y, text, color, opts) 1735{ 1736 color = color || 'black'; ctx.fillStyle = color; 1737 var font = w3_opt(opts, 'font'); if (isDefined(font)) ctx.font = font; 1738 var lineWidth = w3_opt(opts, 'lineWidth'); if (isDefined(lineWidth)) ctx.lineWidth = lineWidth; 1739 var align = w3_opt(opts, 'align'); if (isDefined(align)) ctx.textAlign = align; 1740 var baseline = w3_opt(opts, 'baseline'); if (isDefined(baseline)) ctx.textBaseline = baseline; 1741 1742 var tw = ctx.measureText(text).width; 1743 if (!w3_opt(opts, 'left')) x -= tw/2; // i.e. centered 1744 ctx.fillText(text, x, y); 1745} 1746 1747function w3_check_restart_reboot(el_id) 1748{ 1749 var el = w3_el(el_id); 1750 if (!el) return; 1751 1752 w3_iterate_parent(el, function(el) { 1753 if (w3_contains(el, 'w3-restart')) { 1754 w3_restart_cb(); 1755 return el; 1756 } 1757 if (w3_contains(el, 'w3-reboot')) { 1758 w3_reboot_cb(); 1759 return el; 1760 } 1761 return null; 1762 }); 1763} 1764 1765// XXX just an idea -- not working yet 1766function w3_check_cfg_reload(el_id) 1767{ 1768 if (!isAdmin()) return; 1769 var el = w3_el(el_id); 1770 if (!el || !w3_contains(el, 'w3-cfg-restart')) return; 1771 1772 kiwi_clearTimeout(w3int.reload_timeout); 1773 w3int.reload_timeout = setTimeout( 1774 function() { 1775 w3int.update_seq++; 1776 ext_send('ADM reload_cfg'); 1777 console.log('w3 reload_cfg'); 1778 1779 /* XXX this needs to be done in the context of each user connection 1780 setTimeout( 1781 function() { 1782 if (ext_name()) w3_call(ext_name() +'_cfg_reload'); 1783 w3_call('ant_switch_cfg_reload'); 1784 }, 5000); 1785 */ 1786 }, 5000); 1787} 1788 1789function w3_set_value(path, val) 1790{ 1791 //console.log('w3_set_value path='+ path +' val='+ val); 1792 path = w3_add_id(path); 1793 if (1) { 1794 // set all similarly named elements 1795 w3_els(path, function(el) { 1796 if (el) el.value = val; 1797 }); 1798 } else { 1799 var el = w3_el(path); 1800 /* 1801 if (val == '(encrypted)' && w3_contains(el, 'w3-encrypted')) 1802 w3_add(el, 'kiwi-pw'); 1803 */ 1804 if (el) el.value = val; 1805 } 1806} 1807 1808function w3_set_decoded_value(path, val) 1809{ 1810 //console.log('w3_set_decoded_value: path='+ path +' val='+ val); 1811 w3_set_value(path, kiwi_decodeURIComponent('w3_set_decoded_value:'+ path, val)); 1812} 1813 1814function w3_get_value(path) 1815{ 1816 var el = w3_el(path); 1817 if (!el) return null; 1818 return el.value; 1819} 1820 1821function w3_placeholder(path, val) 1822{ 1823 var el = w3_el(path); 1824 if (!el) return null; 1825 w3_set_value(el, ''); 1826 w3_attribute(el, 'placeholder', val); 1827} 1828 1829function w3_add_toplevel(path) 1830{ 1831 if (!path.startsWith('cfg.') && !path.startsWith('adm.')) 1832 return 'cfg.'+ path; 1833 return path; 1834} 1835 1836function w3_not_toplevel(path) 1837{ 1838 if (path.startsWith('cfg.') || path.startsWith('adm.')) 1839 return path.substr(path.indexOf('.') + 1); 1840 return path; 1841} 1842 1843function w3_basename(path) 1844{ 1845 var i = path.lastIndexOf('.'); 1846 if (i >= 0) { 1847 return path.substr(i+1); 1848 } 1849 return path; 1850} 1851 1852// add 'c' between string arguments 1853function w3_sbc(c) 1854{ 1855 var a = []; 1856 var strnum = function(s) { 1857 s = isString(s)? s.trim() : (isNumber(s)? s.toString() : ''); 1858 if (s != '') a.push(s); 1859 }; 1860 for (var i = 1; i < arguments.length; i++) { 1861 var s = arguments[i]; 1862 if (isObject(s)) { // e.g. from w3_sb(), w3_cat() below 1863 w3_obj_enum(s, function(k,i,o) { 1864 strnum(o); 1865 }); 1866 } else { 1867 strnum(s); 1868 } 1869 } 1870 return a.length? a.join(c) : ''; 1871} 1872 1873function w3_sb() { return w3_sbc(' ', arguments); } 1874 1875function w3_cat() { return w3_sbc('', arguments); } 1876 1877// "space between ending with" 1878function w3_sbew(ew) 1879{ 1880 var a = []; 1881 for (var i = 1; i < arguments.length; i++) { 1882 var s = arguments[i]; 1883 s = isString(s)? s.trim() : (isNumber(s)? s.toString() : ''); 1884 if (s != '') { 1885 if (s[s.length-1] != ew) s = s + ew; 1886 a.push(s); 1887 } 1888 } 1889 return a.length? a.join(' ') : ''; 1890} 1891 1892// append \n if s is not empty 1893function w3_nl(s) 1894{ 1895 if (isArg(s) && s != '') s += '\n'; 1896 return s; 1897} 1898 1899// append <br> if s is not empty 1900function w3_br(s) 1901{ 1902 if (isArg(s) && s != '') s += '<br>'; 1903 return s; 1904} 1905 1906// psa = prop|style|attr 1907// => [class="[prop] [extra_prop]"] [style="[style] [extra_style]"] [attr] [extra_attr] 1908function w3_psa(psa, extra_prop, extra_style, extra_attr) 1909{
1910 var dump = psa && psa.includes('w3-dump'); 1911 if (dump) { 1912 console.log('psa_in=['+ psa +']'); 1913 console.log('extra_prop=['+ extra_prop +']'); 1914 console.log('extra_style=['+ extra_style +']'); 1915 console.log('extra_attr=['+ extra_attr +']'); 1916 } 1917 1918 if (psa && psa.startsWith('class=')) { 1919 //console.log('#### w3_psa RECURSION ####'); 1920 return psa; // already processed 1921 } 1922 1923 var a = psa? psa.split('|') : []; 1924 psa = ''; 1925 1926 var prop = a[0] || ''; 1927 if (extra_prop) prop = w3_sb(prop, extra_prop); 1928 if (prop != '') psa = 'class='+ dq(prop); 1929 1930 var style = a[1] || ''; 1931 if (extra_style) style = w3_sbew(';', style, extra_style); 1932 if (style != '') psa = w3_sb(psa, 'style='+ dq(style)); 1933 1934 var attr = (a[2] || '').replace(/&vbar;/g, '|'); // e.g. title="foo&vbar;bar" => title="foo|bar" 1935 if (extra_attr) attr = w3_sb(attr, extra_attr); 1936 if (attr != '') psa = w3_sb(psa, attr); 1937 1938 if (dump) console.log('psa_out=['+ psa +']'); 1939 return psa; 1940} 1941 1942// given as input: 1943// 'right' 1944// 'middle/right' 1945// 'left/middle/right' 1946// returns: 1947// { left:'...', middle:'...', right:'...', num:[0123] } 1948function w3_psa3(psa3) 1949{ 1950 //if (psa3.includes('w3-dump')) console.log('w3_psa3 in=['+ psa3 +']'); 1951 psa3 = psa3 || ''; 1952 1953 var a = psa3.split('||'); 1954 if (a.length == 2) { 1955 if (a[1].includes('/')) { 1956 var s = a[1].replace(/\//g, '&slash;'); 1957 // e.g. ...||... title="foo/bar instead of foo&slash;bar" 1958 console.log('w3_psa3 psa='+ sq(psa3)); 1959 console.log('w3_psa3 DANGER: psa attribute part after "||" contained "/", replaced with "&slash;"'); 1960 console.log("w3_psa3 before: '||"+ a[1] +"'"); 1961 console.log("w3_psa3 after: '||"+ s +"'"); 1962 psa3 = a[0] +'||'+ s; 1963 } 1964 } 1965 1966 a = psa3.split('/'); 1967 var a0 = (a[0] || '').replace(/&slash;/g, '/'); 1968 var a1 = (a[1] || '').replace(/&slash;/g, '/'); 1969 var a2 = (a[2] || '').replace(/&slash;/g, '/'); 1970 //if (psa3.includes('w3-dump')) console.log(a); 1971 1972 if (a.length == 1) 1973 return { left:'', middle:'', right:a0, num:1 }; 1974 else
1975 if (a.length == 2) 1976 return { left:'', middle:a0, right:a1, num:2 }; 1977 else 1978 if (a.length == 3) 1979 return { left:a0, middle:a1, right:a2, num:3 }; 1980 else 1981 return { left:'', middle:'', right:'', num:0 }; 1982} 1983 1984// like w3_psa() except returns in original psa format (i.e. not expanded to "class=...") 1985// NB: does not handle left/middle/right notation -- only p|s|a 1986function w3_psa_mix(psa, extra_prop, extra_style, extra_attr) 1987{ 1988 var dump = psa.includes('w3-dump'); 1989 if (dump) console.log('--- w3_psa_mix psa='+ psa); 1990 var a = psa? psa.split('|') : []; 1991 1992 var prop = a[0] || ''; 1993 if (dump) console.log('prop=['+ prop +']'); 1994 if (dump) console.log('extra_prop=['+ extra_prop +']'); 1995 if (extra_prop) prop = w3_sb(prop, extra_prop); 1996 if (dump) console.log('mixed prop=['+ prop +']'); 1997 1998 var style = a[1] || ''; 1999 if (dump) console.log('style=['+ style +']'); 2000 if (dump) console.log('extra_style=['+ extra_style +']'); 2001 if (extra_style) style = w3_sbew(';', style, extra_style); 2002 if (dump) console.log('mixed style=['+ style +']'); 2003 2004 var attr = a[2] || ''; // NB: don't do &vbar; => | expansion here since output is in psa format 2005 if (dump) console.log('attr=['+ attr +']'); 2006 if (dump) console.log('extra_attr=['+ extra_attr +']'); 2007 if (extra_attr) attr = w3_sb(attr, extra_attr); 2008 if (dump) console.log('mixed attr=['+ attr +']'); 2009 2010 psa = prop +'|'+ style +'|'+ attr; 2011 if (dump) console.log('mix_out=['+ psa +']'); 2012 return psa; 2013} 2014 2015function w3_set_psa(el, psa) 2016{ 2017 el = w3_el(el); 2018 if (!el || isEmptyString(psa)) return null; 2019 psa = psa.split('/')[0]; 2020 psa = psa.split('|'); 2021 var props = psa[0]; 2022 var styles = psa[1]; 2023 var attrs = psa[2]; 2024 if (isArg(props)) w3_add(el, props); 2025 if (isArg(styles)) el.style = styles; 2026 if (isArg(attrs)) w3_set_attributes_from_string(el, attrs); 2027} 2028 2029function w3int_init() 2030{ 2031} 2032 2033function w3int_post_action() 2034{ 2035 // if it exists, re-select the main page frequency field 2036 //console.log('w3int_post_action freqset_select'); 2037 w3_call('freqset_select'); 2038} 2039 2040function w3_isScrollingY(id) 2041{ 2042 var el = w3_el(id); 2043 if (!el) return null; 2044 return (el.scrollHeight > el.clientHeight); 2045} 2046 2047function w3_isScrollingX(id) 2048{ 2049 var el = w3_el(id); 2050 if (!el) return null; 2051 return (el.scrollWidth > el.clientWidth); 2052} 2053 2054// see: developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled 2055function w3_isScrolledDown(id) 2056{ 2057 var el = w3_el(id); 2058 if (!el) return null; 2059 return (Math.abs(el.scrollHeight - el.clientHeight - el.scrollTop) < 1); 2060} 2061 2062// returns 0 to 1.0 (-1 on error) 2063function w3_scrolledPosition(id) 2064{ 2065 var el = w3_el(id); 2066 if (!el) return null; 2067 //console.log('w3_scrolledPosition scrollTop='+ el.scrollTop +' scrollHeight='+ el.scrollHeight +' clientHeight='+ el.clientHeight); 2068 if (el.clientHeight == 0) return -1; // element hidden most likely 2069 var scrollH = el.scrollHeight - el.clientHeight; 2070 if (scrollH <= 0) return 0; // no scrollbar, so always at top 2071 return el.scrollTop / scrollH; 2072} 2073 2074function w3_scrollTo(id, pos, jog) 2075{ 2076 var el = w3_el(id); 2077 if (!el) return null; 2078 var scrollH = el.scrollHeight - el.clientHeight; 2079 if (scrollH < 0) scrollH = 0; // no scrollbar, so always at top 2080 var top = scrollH * pos; 2081 var unchanged = (top == el.scrollTop); 2082 //console.log('w3_scrollTo: unchanged='+ unchanged +' pos='+ pos +' sH='+ el.scrollHeight +' cH='+ el.clientHeight +' top='+ top +' scrollTop='+ el.scrollTop); 2083 el.scrollTop = top; 2084 2085 // jog to force scroll event 2086 if (jog == true) { 2087 el.scrollTop = top+1; 2088 el.scrollTop = top; 2089 } 2090 return unchanged; 2091} 2092 2093// id element must have scroll property 2094function w3_scrollDown(id, cond) 2095{ 2096 var el = w3_el(id); 2097 if (isArg(cond)? cond : true) el.scrollTop = el.scrollHeight; 2098 return el; 2099} 2100 2101// id element must have scroll property 2102function w3_scrollTop(id, cond) 2103{ 2104 var el = w3_el(id); 2105 if (isArg(cond)? cond : true) el.scrollTop = 0; 2106 return el; 2107} 2108 2109// calls func(arg) when w3_el(id) exists in DOM 2110function w3_do_when_rendered(el_id, func, arg, poll_ms) 2111{ 2112 w3_do_when_cond( 2113 function() { return isArg(w3_el(el_id)); }, 2114 func, arg, poll_ms 2115 ); 2116} 2117 2118// calls func(arg) when w3_el(id).clientHeight is non-zero 2119function w3_do_when_clientHeight(el_id, func, arg, poll_ms) 2120{ 2121 w3_do_when_cond( 2122 function() { 2123 var el = w3_el(el_id); 2124 if (!el) return false;
2125 return (el.clientHeight != 0); 2126 }, func, arg, poll_ms 2127 ); 2128} 2129 2130// calls func(arg) when cond_func() becomes true 2131function w3_do_when_cond(cond_func, func, arg, poll_ms) 2132{ 2133 poll_ms = isNumberElse(poll_ms, 100); 2134 if (!cond_func(arg)) { 2135 setTimeout(function () { 2136 w3_do_when_cond(cond_func, func, arg, poll_ms); 2137 }, poll_ms); 2138 } else { 2139 w3_call(func, arg); 2140 } 2141 2142 // REMINDER: Other than the FIRST call, returns from here don't go anywhere. 2143 // In particular the original caller is returned to: 2144 // 1) If the FIRST check of cond_func() is true and after func() runs. 2145 // 2) If the FIRST check of cond_func() is false and after the first setTimeout() runs. 2146 // But the caller is NOT suspended or somehow waiting for a delayed run of func() when 2147 // conf_func() is false for some number of setTimeout() periods. 2148 // That's not how javascript works (Web Workers aside). It is event driven and all threads 2149 // must complete without conditional delay. 2150} 2151 2152// w3_elementAtPointer(evt) 2153// w3_elementAtPointer(x, y) 2154function w3_elementAtPointer(x, y) 2155{ 2156 if (isObject(x)) { 2157 var evt = x; 2158 x = evt.pageX; y = evt.pageY; 2159 } 2160 if (isNumber(x) && isNumber(y)) { 2161 return document.elementFromPoint(x,y); 2162 } 2163 return null; 2164} 2165 2166function w3_event_log(el, id, event_name) 2167{ 2168 el = w3_el(el); 2169 if (el == null) return; 2170 el.addEventListener(event_name, 2171 function() { 2172 console.log('w3_event_log '+ id +': '+ event_name); 2173 //canvas_log(event_name); 2174 }, w3.BUBBLING 2175 ); 2176} 2177 2178// observe element event behavior 2179function w3_event_listener(id, el) 2180{ 2181 el = w3_el(el); 2182 if (!el) return null; 2183 if (!w3int.eventListener[id]) { 2184 w3_event_log(el, id, 'mouseup'); 2185 w3_event_log(el, id, 'mousedown'); 2186 w3_event_log(el, id, 'click'); 2187 w3_event_log(el, id, 'change'); 2188 w3_event_log(el, id, 'submit'); 2189 w3_event_log(el, id, 'focus'); 2190 w3_event_log(el, id, 'blur'); 2191 w3int.eventListener[id] = true; 2192 } 2193} 2194 2195// Return canvas-relative mouse x/y for a canvas potentially scrolling due to overflow. 2196// Mouse x/y in canvas CSS pixels (handles DPI / CSS scaling). 2197function w3_canvas_event_xy(evt, canvas) 2198{ 2199 var el = w3_el(canvas); 2200 var rect = el.getBoundingClientRect(); 2201 var sx = rect.width? (el.width / rect.width) : 1; 2202 var sy = rect.height? (el.height / rect.height) : 1; 2203 var x, y; 2204 if (isArg(evt.offsetX) && isArg(evt.offsetY) && evt.target === el) { 2205 x = evt.offsetX * sx; 2206 y = evt.offsetY * sy; 2207 //console.log('used offsetXY'); 2208 } else { 2209 x = (evt.pageX - rect.left) * sx; 2210 y = (evt.pageY - rect.top) * sy; 2211 //console.log('used pageXY'); 2212 } 2213 //console.log({ t:'w3_canvas_event_xy', sx:sx, sy:sy, x:x, y:y }); 2214 return { x:x, y:y, rect:rect }; 2215} 2216 2217function w3_xy_within(start, stop, delta) 2218{ 2219 //console.log('w3_xy_within'); 2220 //console.log(start); 2221 //console.log(stop); 2222 var rv = 2223 (Math.abs(stop.x - start.x) <= delta) && 2224 (Math.abs(stop.y - start.y) <= delta); 2225 //console.log('rv='+ rv); 2226 return rv; 2227} 2228 2229 2230//////////////////////////////// 2231// hr
2232//////////////////////////////// 2233 2234function w3_hr(psa) 2235{ 2236 var p = w3_psa(psa); 2237 var s = '<hr '+ p +'>'; 2238 var narg = arguments.length; 2239 for (var i=1; i < narg; i++) { 2240 s += arguments[i]; 2241 } 2242 //console.log(s); 2243 return s; 2244} 2245 2246 2247//////////////////////////////// 2248// nav 2249//////////////////////////////// 2250 2251function w3_click_nav(nav_id, next_id, cb_next, cb_param, from) 2252{ 2253 //console.log('--------'); 2254 //console.log('w3_click_nav START nav_id='+ nav_id +' next_id='+ next_id +' cb_next='+ cb_next +' cb_param='+ cb_param +' from='+ from); 2255 var next_id_nav = ('id-nav-'+ next_id).toLowerCase(); // to differentiate the nav anchor from the nav container 2256 var cur_id = null; 2257 var next_el = null; 2258 var cb_prev = null; 2259 var found = 0; 2260 2261 var el = w3_el(nav_id); 2262 //console.log('w3_click_nav nav_id='+ nav_id +' el='+ el); 2263 if (!el) return; 2264 2265 w3_iterate_children(el, function(el, i) { 2266 //console.log('w3_click_nav consider: nodename='+ el.nodeName); 2267 if (el.nodeName != 'A' || found >= 2) return; 2268 //if (el.nodeName != 'DIV') return; 2269 2270 //console.log('w3_click_nav consider: id=('+ el.id +') ==? next_id=('+ next_id_nav +') el.className="'+ el.className +'"'); 2271 if (w3_contains(el, 'w3int-cur-sel')) { 2272 cur_id = el.id.substring(7); // remove 'id-nav-' added by w3int_anchor() 2273 2274 //console.log('w3_click_nav FOUND-CUR cur_id='+ cur_id); 2275 w3_remove(el, 'w3int-cur-sel'); 2276 w3_iterate_classList(el, function(s, i) { 2277 if (s.startsWith('id-nav-cb-')) 2278 cb_prev = s.substring(10); 2279 }); 2280 found++; 2281 } 2282 2283 if (el.id.toLowerCase().startsWith(next_id_nav)) { 2284 //console.log('w3_click_nav FOUND-NEXT id='+ next_id); 2285 next_id = el.id.split('id-nav-')[1]; 2286 next_el = el; 2287 found++; 2288 } 2289 }); 2290 2291 // toggle visibility of current content 2292 if (cur_id && cb_prev) { 2293 //console.log('w3_click_nav BLUR cb_prev='+ cb_prev +' cur_id='+ cur_id); 2294 w3_call(cb_prev +'_blur', cur_id); 2295 } 2296 if (cur_id) 2297 w3_toggle(cur_id, 'w3-show-block'); 2298 2299 // make new nav item current and visible / focused 2300 if (next_el) { 2301 w3_add(next_el, 'w3int-cur-sel'); 2302 w3_check_restart_reboot(next_el); 2303 } 2304 2305 w3_toggle(next_id, 'w3-show-block'); 2306 if (cb_next != 'null') { 2307 //console.log('w3_click_nav FOCUS cb_next='+ cb_next +' next_id='+ next_id); 2308 w3_call(cb_next +'_focus', next_id, cb_param); 2309 } 2310 //console.log('w3_click_nav DONE cb_prev='+ cb_prev +' cur_id='+ cur_id +' cb_next='+ cb_next +' next_id='+ next_id); 2311} 2312 2313// id = unique, cb = undefined => cb = id 2314// id = unique, cb = func 2315// id = unique, cb = null => don't want focus/blur callbacks 2316function w3int_anchor(psa, text, navbar, id, cb, isSelected) 2317{ 2318 if (cb === undefined) cb = id; 2319 var nav_cb = cb? ('id-nav-cb-'+ cb) : ''; 2320 //console.log('w3int_anchor id='+ id +' cb='+ cb +' nav_cb='+ nav_cb); 2321 2322 // store id prefixed with 'id-nav-' so as not to collide with content container id prefixed with 'id-' 2323 var attr = 'id="id-nav-'+ id +'" onclick="w3_click_nav('+ sq(navbar) +', '+ sq(id) +', '+ sq(cb) +')"'; 2324 //console.log('w3int_anchor psa: '+ psa); 2325 //console.log('w3int_anchor attr: '+ attr); 2326 var p = w3_psa(psa, nav_cb + (isSelected? ' w3int-cur-sel':''), '', attr); 2327 //var p = w3_psa(psa, 'w3-show-inline '+ nav_cb + (isSelected? ' w3int-cur-sel':''), '', attr); 2328 //console.log('w3int_anchor p: '+ p); 2329 2330 // store with id= instead of a class property so it's easy to find with el.id in w3_iterate_classname() 2331 // href of "javascript:void(0)" instead of "#" so page doesn't scroll to top on click 2332 var s = '<a '+ p +' href="javascript:void(0)">'+ text +'</a>'; 2333 //var s = w3_div(p, text); 2334 //console.log('w3int_anchor: '+ s); 2335 return s; 2336} 2337 2338function w3_navbar(psa) 2339{ 2340 var p = w3_psa(psa, 'w3-navbar'); 2341 var s = '<nav '+ p +'>'; 2342 var narg = arguments.length; 2343 for (var i=1; i < narg; i++) { 2344 s += arguments[i]; 2345 } 2346 s += '</nav>'; 2347 //console.log(s); 2348 return s; 2349} 2350 2351function w3_sidenav(psa) 2352{ 2353 var p = w3_psa(psa, 'w3-sidenav w3-static w3-left w3-sidenav-full-height w3-light-grey'); 2354 var s = '<nav '+ p +'>'; 2355 var narg = arguments.length; 2356 for (var i=1; i < narg; i++) { 2357 s += arguments[i]; 2358 } 2359 s += '</nav>'; 2360 //console.log(s); 2361 return s; 2362} 2363 2364function w3_nav(psa, text, navbar, id, cb, isSelected) 2365{ 2366 return w3int_anchor(psa, text, navbar, id, cb, isSelected); 2367} 2368 2369function w3_navdef(psa, text, navbar, id, cb) 2370{ 2371 // must wait until instantiated before manipulating 2372 setTimeout(function() { 2373 //console.log('w3_navdef instantiate focus'); 2374 w3_toggle(id, 'w3-show-block'); 2375 }, w3_highlight_time); 2376 2377 return w3int_anchor(psa, text, navbar, id, cb, true); 2378} 2379 2380 2381//////////////////////////////// 2382// labels 2383//////////////////////////////// 2384 2385function w3int_label(psa, text, path, extension) 2386{ 2387 psa = psa || ''; 2388 text = text || ''; 2389 extension = extension || '';
2390 var dump = psa.includes('w3-dump'); 2391 2392 // don't emit empty label div 2393 //if (psa == '' && text == '' && extension == '') return ''; 2394 if (text == '' && ((psa != '' && psa != '||') || extension != '')) { 2395 console.log('$w3int_label', {text, psa, extension}); 2396 //kiwi_trace(); 2397 } 2398 if (text == '') { 2399 if (dump) console.log('$w3int_label <empty string>'); 2400 return ''; 2401 } 2402 2403 // most likely already an embedded w3_label() 2404 if (text.startsWith('<label ')) { 2405 if (dump) console.log('$w3int_label EMBEDDED '+ text); 2406 return text; 2407 } 2408 2409 var id = w3_add_id(path, '-label'); // so w3_set_label() can find label 2410 //var inline = psa.includes('w3-label-inline'); 2411 var p = w3_psa(psa, id); 2412 var s = '<label '+ p +'>'+ text + extension +'</label>'; 2413 //var s = '<label '+ p +'>'+ text + extension + (inline? '':'<br>') +'</label>'; 2414 if (dump) console.log('$w3int_label psa='+ psa +' text=<'+ text +'> s=<'+ s +'>'); 2415 return s; 2416} 2417 2418// if called directly always emit label div even if text is empty 2419function w3_label(psa, text, path, extension) 2420{ 2421 if (arguments.length >= 4) { 2422 console.log('### w3_label ext='+ extension); 2423 //kiwi_trace(); 2424 } 2425 if (isEmptyString(text)) text = ' '; 2426 return w3int_label(psa, text, path, extension); 2427} 2428 2429function w3_get_label(label, path) 2430{ 2431 return w3_get_innerHTML(w3_add_id(path, '-label')); 2432} 2433 2434function w3_set_label(label, path) 2435{ 2436 w3_innerHTML(w3_add_id(path, '-label'), label); 2437} 2438 2439 2440//////////////////////////////// 2441// title 2442//////////////////////////////// 2443 2444function w3_title(id, text) 2445{ 2446 //console.log('w3_title id='+ id +' text='+ text); 2447 var el = w3_el(id); 2448 if (!el) return null; 2449 w3_attribute(el, 'title', text); 2450 return el; 2451} 2452 2453function w3_title_multi(id, text) 2454{ 2455 w3_els(id, function(el, i) { el.title = text; } ); 2456} 2457 2458 2459//////////////////////////////// 2460// link 2461//////////////////////////////// 2462 2463function w3int_link_click(ev, cb, cb_param) 2464{ 2465 //console.log('w3int_link_click cb='+ cb +' cb_param='+ cb_param); 2466 //console.log(ev); 2467 var el = ev.currentTarget; 2468 //console.log(el); 2469 w3_check_restart_reboot(el); 2470 2471 // cb is a string because can't pass an object to onclick 2472 if (cb) { 2473 w3_call(cb, el, cb_param, /* first */ false); // links don't have first callback 2474 } 2475 2476 w3int_post_action(); 2477} 2478 2479function w3_link(psa, url, inner, title, cb, cb_param) 2480{ 2481 var qual_url = url; 2482 var href = ''; 2483 var target = ''; 2484 if (url != '') { 2485 if (!url.startsWith('javascript:')) { 2486 if (!url.startsWith('http://') && !url.startsWith('https://')) 2487 qual_url = 'http://'+ url; 2488 target = ' target="_blank"'; 2489 } 2490 href = 'href='+ dq(qual_url) +' '; 2491 } 2492 inner = inner || ''; 2493 title = title || ''; 2494 if (title != '') title = ' title='+ dq(title); 2495 2496 // by default use pointer cursor if there is a callback 2497 var pointer = (cb && cb != '')? 'w3-pointer':''; 2498 cb_param = cb_param || 0; 2499 var onclick = cb? (' onclick="w3int_link_click(event, '+ sq(cb) +', '+ sq(cb_param) +')"') : ''; 2500 2501 var p = w3_psa(psa, pointer, '', href + target + title + onclick); 2502 2503 // escape HTML '<' and '>' so they work with kiwi_output_msg() 2504 var esc_html = psa.includes('w3-esc-html'); 2505 var lt = esc_html? kiwi.esc_lt : '<'; 2506 var gt = esc_html? kiwi.esc_gt : '>'; 2507 var s = lt +'a '+ p + gt + inner + lt +'/a'+ gt; 2508 //console.log(s); 2509 return s; 2510} 2511 2512function w3_link_set(path, url) 2513{ 2514 var el = w3_el(path); 2515 //console.log('w3_link_set: path='+ path +' url='+ url); 2516 //console.log(el); 2517 if (el && url != '') { 2518 if (!url.startsWith('http://') && !url.startsWith('https://')) 2519 url = 'http://'+ url; 2520 w3_attribute(el, 'href', url); 2521 w3_attribute(el, 'target', '_blank'); 2522 } 2523} 2524 2525 2526//////////////////////////////// 2527// image 2528//////////////////////////////// 2529 2530function w3_img_wh(src, func) 2531{ 2532 // see: stackoverflow.com/questions/318630/get-the-real-width-and-height-of-an-image-with-javascript-in-safari-chrome 2533 // There are huge issues obtaining the true height/width of an img given variable state during loading (caching etc). 2534 var img = new Image(); 2535 img.onload = function() { 2536 //console.log('w3_img_wh '+ this.width +'x'+ this.height +' src='+ src); 2537 func(this.width, this.height); 2538 }; 2539 img.src = src; 2540} 2541 2542function w3_img(psa, file, width, height) 2543{ 2544 var w = isArg(width)? ('width='+ width) : ''; 2545 var h = isArg(height)? ('height='+ height) : ''; 2546 var src = 'src='+ dq(file); 2547 var p = w3_psa(psa, null, null, w3_sb(src, w, h)); 2548 var s = '<img '+ p +' />';
2549 if (psa && psa.includes('w3-dump')) console.log('w3-dump: '+ s); 2550 return s; 2551} 2552 2553 2554//////////////////////////////// 2555// buttons: radio 2556//////////////////////////////// 2557 2558var w3_SELECTED = true; 2559var w3_NOT_SELECTED = false; 2560 2561function w3_radio_unhighlight(path) 2562{ 2563 w3_iterate_classname(w3_add_id(path), function(el) { w3_unhighlight(el); }); 2564} 2565 2566function w3int_radio_click(ev, path, cb, cb_param) 2567{ 2568 //console.log('w3int_radio_click path='+ path +' cb='+ cb +' cb_param='+ cb_param); 2569 w3_radio_unhighlight(path); 2570 w3_highlight(ev.currentTarget); 2571 2572 var idx = -1; 2573 w3_iterate_classname(w3_add_id(path), function(el, i) { 2574 if (w3_isHighlighted(el)) 2575 idx = i; 2576 //console.log('w3int_radio_click CONSIDER path='+ path +' el='+ el +' idx='+ idx); 2577 }); 2578 2579 w3_check_restart_reboot(ev.currentTarget); 2580 2581 // cb is a string because can't pass an object to onclick 2582 if (cb) { 2583 w3_call(cb, path, idx, /* first */ false, cb_param); // radio buttons don't have first callback 2584 } 2585 2586 w3int_post_action(); 2587} 2588 2589function w3_radio_button(psa, text, path, isSelected, cb, cb_param) 2590{ 2591 cb_param = cb_param || 0; 2592 var onclick = cb? ('onclick="w3int_radio_click(event, '+ sq(path) +', '+ sq(cb) +', '+ sq(cb_param) +')"') : ''; 2593 var p = w3_psa(psa, w3_add_id(path) + (isSelected? (' '+ w3_highlight_color) : '') +' w3-btn w3-ext-btn', '', onclick); 2594 var s = '<button '+ p +'>'+ text +'</button>'; 2595 //console.log(s); 2596 return s; 2597} 2598 2599// used when current value should come from config param 2600function w3_radio_button_get_param(psa, text, path, selected_if_val, init_val, cb, cb_param) 2601{ 2602 // FIXME 2603 //var update_path_var = psa.includes('w3-update'); 2604 var save = psa.includes('w3-defer')? EXT_SAVE_DEFER : undefined; 2605 //console.log('w3_radio_button_get_param: '+ path); 2606 var cur_val = ext_get_cfg_param(path, (init_val == undefined)? null : init_val, save); 2607 2608 // set default selection of button based on current value 2609 var isSelected = (cur_val == selected_if_val)? w3_SELECTED : w3_NOT_SELECTED; 2610 return w3_radio_button(psa, text, path, isSelected, cb, cb_param); 2611} 2612 2613 2614//////////////////////////////// 2615// switch: two button switch 2616//////////////////////////////// 2617 2618var w3_SWITCH_YES_IDX = 0, w3_SWITCH_NO_IDX = 1; 2619 2620function w3_switch_s(idx) { return ((idx == w3_SWITCH_YES_IDX)? 'YES' : 'NO'); } 2621 2622function w3_switch_val2idx(val) { return (val? w3_SWITCH_YES_IDX : w3_SWITCH_NO_IDX); } 2623 2624function w3_switch_idx2val(idx) { return ((idx == w3_SWITCH_YES_IDX)? 1:0); } 2625 2626function w3_switch(psa, text_0, text_1, path, text_0_selected, cb, cb_param) 2627{ 2628 //console.log('w3_switch psa='+ psa); 2629 var s = 2630 w3_radio_button(w3_psa_mix(psa, 'w3int-switch-0'), text_0, path, text_0_selected? 1:0, cb, cb_param) + 2631 w3_radio_button(w3_psa_mix(psa, 'w3int-switch-1'), text_1, path, text_0_selected? 0:1, cb, cb_param); 2632 return s; 2633} 2634 2635function w3_switch_label(psa, label, text_0, text_1, path, text_0_selected, cb, cb_param) 2636{ 2637 //console.log('w3_switch_label psa='+ psa); 2638 var hasLabel = (label != ''); 2639 var inline = psa.includes('w3-label-inline'); 2640 var centered = psa.includes('w3-center'); 2641 var left = psa.includes('w3-label-left'); 2642 var bold = !psa.includes('w3-label-not-bold'); 2643 2644 var spacing = (hasLabel && !inline)? 'w3-margin-T-8' : ''; 2645 if (hasLabel && inline) spacing += left? ' w3-margin-L-8' : ' w3-margin-R-8'; 2646 if (centered) spacing += ' w3-halign-center'; 2647 2648 var psa3 = w3_psa3(psa); 2649 var psa_outer = w3_psa_mix(psa3.left, (inline? 'w3-inline-flex':'') + (centered? ' w3-center':'')); 2650 var psa_label = w3_psa_mix(psa3.middle, (hasLabel && bold)? 'w3-bold':''); 2651 var psa_inner = w3_psa(); 2652 2653 var ls = w3int_label(psa_label, label, path); 2654 var cs = 2655 w3_inline(spacing +'/'+ psa3.right, 2656 w3_radio_button('w3int-switch-0', text_0, path, text_0_selected? 1:0, cb, cb_param) + 2657 w3_radio_button('w3int-switch-1', text_1, path, text_0_selected? 0:1, cb, cb_param) 2658 ); 2659 var s = w3_div(psa_outer, ((left || !inline)? (ls + cs) : (cs + ls))); 2660 return s; 2661} 2662 2663// used when current value should come from config param 2664function w3_switch_get_param(psa, text_0, text_1, path, text_0_selected_if_val, init_val, cb, cb_param) 2665{ 2666 // FIXME 2667 //var update_path_var = psa.includes('w3-update');
2668 var save = psa.includes('w3-defer')? EXT_SAVE_DEFER : undefined; 2669 var cur_val = ext_get_cfg_param(path, (init_val == undefined)? null : init_val, save); 2670 2671 // set default selection of button based on current value 2672 //if (w3_contains(psa, 'id-net-ssl')) 2673 // console.log('w3_switch_get_param cur_val='+ cur_val +' text_0_selected_if_val='+ text_0_selected_if_val); 2674 var text_0_selected = (cur_val == text_0_selected_if_val)? w3_SELECTED : w3_NOT_SELECTED; 2675 return w3_switch(psa, text_0, text_1, path, text_0_selected, cb, cb_param); 2676} 2677 2678// used when current value should come from config param 2679function w3_switch_label_get_param(psa, label, text_0, text_1, path, text_0_selected_if_val, init_val, cb, cb_param) 2680{ 2681 // FIXME 2682 //var update_path_var = psa.includes('w3-update'); 2683 var save = psa.includes('w3-defer')? EXT_SAVE_DEFER : undefined; 2684 var cur_val = ext_get_cfg_param(path, (init_val == undefined)? null : init_val, save); 2685 2686 // set default selection of button based on current value 2687 //if (w3_contains(psa, 'id-net-ssl')) 2688 // console.log('w3_switch_get_param cur_val='+ cur_val +' text_0_selected_if_val='+ text_0_selected_if_val); 2689 var text_0_selected = (cur_val == text_0_selected_if_val)? w3_SELECTED : w3_NOT_SELECTED; 2690 return w3_switch_label(psa, label, text_0, text_1, path, text_0_selected, cb, cb_param); 2691} 2692 2693function w3_switch_set_value(path, switch_idx) 2694{ 2695 var sw = 'w3int-switch-'+ switch_idx; 2696 //console.log('w3_switch_set_value: switch='+ sw +' path='+ path); 2697 w3_iterate_classname(path, function(el, i) { 2698 //console.log('w3_switch_set_value: CONSIDER i='+ i); 2699 //console.log(el); 2700 if (w3_contains(el, sw)) { 2701 //console.log('w3_switch_set_value: click()...'); 2702 //console.log(el); 2703 el.click(); 2704 } 2705 }); 2706} 2707 2708 2709//////////////////////////////// 2710// buttons: single, clickable icon 2711//////////////////////////////// 2712 2713function w3int_btn_evt(ev, path, cb, cb_param) 2714{ 2715 var el = w3_el(path); 2716 if (!w3_contains(el, 'w3-disabled')) { 2717 //console.log('w3int_btn_evt ev.type='+ ev.type +' path='+ path +' cb='+ cb +' cb_param='+ cb_param); 2718 2719 if (w3int.autorepeat_canceller_path != null) { 2720 var diff = (w3int.autorepeat_canceller_path != path); 2721 var same = (w3int.autorepeat_canceller_path == path && ev.type == 'mousedown'); 2722 if (diff || same) { 2723 //console.log(path +' CANCELLED '+ w3int.autorepeat_canceller_path); 2724 //if (kiwi_isMobile()) alert(path +' CANCELLED '+ w3int.autorepeat_canceller_path +' diff='+ TF(diff) +' same='+ TF(same)); 2725 kiwi_clearInterval(w3int.autorepeat_canceller_interval); 2726 w3int.autorepeat_canceller_path = null; 2727 } 2728 } 2729 2730 w3_check_restart_reboot(ev.currentTarget); 2731 2732 var hold = w3_match_wildcard(el, 'w3-hold'); // matches w3-hold and w3-hold-<delay> 2733 var hold_done = w3_contains(el, 'w3-hold-done'); 2734 /* 2735 if (hold != false && cb == 'freq_memory_menu_icon_cb') { 2736 console.error('w3-hold'); 2737 //console.log('path='+ path +' cb='+ cb); 2738 console.log(hold); 2739 console.log('hold_done='+ hold_done); 2740 } 2741 */ 2742 //canvas_log(ev.type + ((hold != false)? ' w3H':'') + (hold_done? '+w3D':'')); 2743 2744 if (hold != false) { 2745 //console.log('w3int_btn_evt HOLD '+ ev.type); 2746 if (ev.type == 'mousedown' || ev.type == 'touchstart') { 2747 el.hold_triggered = false; 2748 var delay = isNumberElse(Math.abs(hold.num), 500); 2749 //console.error('w3-hold: '+ hold.prop +' '+ hold.num +' delay='+ delay); 2750 el.hold_timeout = setTimeout(function() { 2751 //canvas_log('HSU'); 2752 el.hold_triggered = true; 2753 //console.log('w3int_btn_evt HOLD_TRIGGERED, RUN ACTION'); 2754 if (cb) { 2755 //canvas_log('EvHOLD'); 2756 w3_call(cb, path, cb_param, /* first */ false, { type: 'hold' }); 2757 } 2758 }, delay); 2759 return ignore(ev); // don't run callback below 2760 } else { 2761 //canvas_log('HT='+ el.hold_timeout); 2762 if ((ev.type == 'click' || ev.type == 'mouseout' || ev.type == 'touchend') && isArg(el.hold_timeout)) { 2763 //if (ev.type == 'mouseout') console.log('MOUSEOUT-HOLD'); 2764 var timeout = el.hold_timeout; 2765 var triggered = el.hold_triggered; 2766 el.hold_timeout = null; 2767 el.hold_triggered = false;
2768 kiwi_clearTimeout(timeout); 2769 if (triggered) { 2770 //console.log('w3int_btn_evt HOLD_PREV_TRIGGERED, IGNORE CLICK'); 2771 //canvas_log('HTR'); 2772 if (hold_done) { 2773 //canvas_log('EvDONE'); 2774 w3_call(cb, path, cb_param, /* first */ false, { type: 'hold-done' }); 2775 } 2776 w3int_post_action(); 2777 return ignore(ev); 2778 } 2779 //canvas_log('HTO'); 2780 2781 // fall through -- normal mouseup/touchend click before hold timer expired 2782 } 2783 } 2784 } 2785 2786 // ignore spurious mouseout events when w3-hold is enabled 2787 if (ev.type == 'mouseout') { 2788 //console.log('MOUSEOUT-REG'); 2789 return; 2790 } 2791 2792 // cb is a string because can't pass an object to onclick 2793 if (cb) { 2794 //canvas_log('CB'); 2795 w3_call(cb, path, cb_param, /* first */ false, ev); // buttons don't have first callback 2796 } 2797 } 2798 2799 w3int_post_action(); 2800} 2801 2802function w3_autorepeat_canceller(path, interval) 2803{ 2804 w3int.autorepeat_canceller_path = isUndefined(path)? null : path; 2805 w3int.autorepeat_canceller_interval = isUndefined(interval)? null : interval; 2806} 2807 2808function w3_cb_param_encode(cbp) 2809{ 2810 if (isNoArg(cbp)) { 2811 return 0; 2812 } 2813 2814 if (isObject(cbp)) { 2815 return encodeURIComponent(JSON.stringify(cbp)); 2816 } 2817 2818 return cbp; 2819} 2820 2821function w3_cb_param_decode(cbp) 2822{ 2823 if (isNoArg(cbp)) { 2824 return 0; 2825 } 2826 2827 if (isString(cbp)) { 2828 cbp = decodeURIComponent(cbp); 2829 var obj; 2830 if (cbp[0] == '{') { 2831 try { 2832 obj = JSON.parse(cbp); 2833 } catch(ex) { 2834 return cbp; 2835 } 2836 return obj; 2837 } 2838 } 2839 2840 return cbp; 2841} 2842 2843function w3int_btn_event_handlers(psa, path, cb, cb_param, dump) 2844{ 2845 var type; 2846 var momentary = psa.includes('w3-momentary'); 2847 var hold = psa.includes('w3-hold'); 2848 var custom_events = psa.includes('w3-custom-events'); 2849 2850 cb_param = w3_cb_param_encode(cb_param); 2851 var ev_cb = function(cbp) { return sprintf('"w3int_btn_evt(event, %s, %s, %s)"', sq(path), sq(cb), sq(cbp)); }; 2852 var onclick = cb? ('onclick='+ ev_cb(cb_param)) : ''; 2853 2854 if (cb && momentary && !hold && !custom_events) { 2855 onclick += ' onmousedown='+ ev_cb(0); 2856 onclick += ' ontouchstart='+ ev_cb(0); 2857 type = 'MOM'; 2858 } else 2859 if (cb && hold && !momentary && !custom_events) { 2860 onclick += ' onmousedown='+ ev_cb(cb_param); 2861 onclick += ' onmouseout='+ ev_cb(cb_param); // needed to cancel w3-hold 2862 onclick += ' ontouchstart='+ ev_cb(cb_param); 2863 onclick += ' ontouchend='+ ev_cb(cb_param); // mobile doesn't generate a click event 2864 type = 'HOLD'; 2865 } else 2866 if (cb && (custom_events || hold) && !momentary) { 2867 onclick += ' onmouseenter='+ ev_cb(cb_param); 2868 onclick += ' onmouseleave='+ ev_cb(cb_param); 2869 onclick += ' onmousedown='+ ev_cb(cb_param); 2870 onclick += ' onmousemove="ignore(event);"'; 2871 onclick += ' onmouseup="ignore(event);"'; 2872 2873 onclick += ' ontouchstart='+ ev_cb(cb_param); 2874 onclick += ' ontouchmove="ignore(event);"'; 2875 onclick += ' ontouchend='+ ev_cb(cb_param); // mobile doesn't generate a click event 2876 type = 'CUS'; 2877 } else 2878 type = 'DEF'; 2879 2880 if (dump) canvas_log('w3int_btn_event_handlers '+ type); 2881 return onclick; 2882} 2883 2884function w3int_button(psa, path, text, cb, cb_param) 2885{ 2886 var dump = psa.includes('w3-dump'); 2887 var onclick = w3int_btn_event_handlers(psa, path, cb, cb_param, dump); 2888 2889 // w3-round-large listed first so its '!important' can be overriden by subsequent '!important's 2890 var default_style = (psa.includes('w3-round') || psa.includes('w3-circle'))? '' : ' w3-round-6px'; 2891 var noactive = psa.includes('w3-noactive')? ' class-button-noactive w3-ext-btn-noactive' : ''; 2892 var right = psa.includes('w3-btn-right')? 'w3-ialign-right' : ''; 2893 var psa3 = w3_psa3(psa); 2894 var psa_outer = w3_psa(psa3.left, right); 2895 var psa_inner = w3_psa(psa3.right, path +' w3-btn w3-ext-btn'+ default_style + noactive, '', onclick); 2896 if (dump) { 2897 console.log('w3_button'); 2898 console.log(psa3); 2899 console.log('psa_outer='+ psa_outer); 2900 console.log('psa_inner='+ psa_inner); 2901 } 2902 var s = '<button '+ psa_inner +'>
2902'+ text +'</button>'; 2903 if (psa_outer != '') s = '<div '+ psa_outer +'>'+ s +'</div>'; 2904 if (psa_outer.includes('w3-dump')) console.log(s); 2905 return s; 2906} 2907 2908function w3_button(psa, text, cb, cb_param) 2909{ 2910 var path = 'id-btn-grp-'+ w3int.btn_grp_uniq.toString(); 2911 w3int.btn_grp_uniq++; 2912 return w3int_button(psa, path, text, cb, cb_param); 2913} 2914 2915function w3_button_path(psa, path, text, cb, cb_param) 2916{ 2917 return w3int_button(psa, path, text, cb, cb_param); 2918} 2919 2920function w3_button_text(path, text, color_or_add_color, remove_color) 2921{ 2922 var el = w3_el(path); 2923 if (!el) return null; 2924 if (isArg(text)) 2925 el.innerHTML = text; 2926 if (color_or_add_color) { 2927 if (color_or_add_color.startsWith('w3-')) { 2928 w3_remove(el, remove_color); 2929 w3_add(el, color_or_add_color); 2930 } else 2931 el.style.color = color_or_add_color; 2932 } 2933 return el; 2934} 2935 2936 2937//////////////////////////////// 2938// icon 2939//////////////////////////////// 2940 2941function w3_icon(psa, fa_icon, size, color, cb, cb_param) 2942{ 2943 var dump = psa.includes('w3-dump'); 2944 2945 // by default use pointer cursor if there is a callback 2946 var pointer = (isNonEmptyString(cb))? ' w3-pointer':''; 2947 var path = 'id-btn-grp-'+ w3int.btn_grp_uniq.toString(); 2948 w3int.btn_grp_uniq++; 2949 2950 var font_size = null; 2951 if (isNumber(size) && size >= 0) font_size = px(size); 2952 else 2953 if (isString(size)) font_size = size; 2954 font_size = font_size? (' font-size:'+ font_size +';') : ''; 2955 2956 color = color || ''; 2957 var c = color.split('|'); 2958 color = ''; 2959 if (c[0] != '') color = ' color:'+ c[0] +';'; 2960 if (c.length >= 2 && c[1] != '') color += ' background-color:'+ c[1] +';'; 2961 2962 var onclick = w3int_btn_event_handlers(psa, path, cb, cb_param, dump); 2963 2964 var p = w3_psa(psa, path +' w3-ext-icon'+ pointer +' fa '+ fa_icon, font_size + color, onclick); 2965 var s = '<i '+ p +'></i>'; 2966 //console.log(s); 2967 return s; 2968} 2969 2970function w3_spin(el, cond) 2971{ 2972 el = w3_el(el); 2973 if (!el) return null; 2974 cond = isArg(cond)? cond : true; 2975 w3_remove_then_add(el, 'fa-spin', cond? 'fa-spin':''); 2976} 2977 2978/* 2979 2980// prototype of callbacks using ev.currentTarget instead of path 2981// switch to using this someday 2982 2983function w3int_icon_click(ev, cb, cb_param) 2984{ 2985 console.log('w3int_icon_click cb='+ cb +' cb_param='+ cb_param); 2986 var el = ev.currentTarget; 2987 console.log(el); 2988 w3_check_restart_reboot(el); 2989 2990 // cb is a string because can't pass an object to onclick 2991 if (cb) { 2992 w3_call(cb, el, cb_param, false); 2993 } 2994 2995 w3int_post_action(); 2996} 2997 2998function w3_icon_cb2(psa, fa_icon, size, color, cb, cb_param) 2999{ 3000 // by default use pointer cursor if there is a callback 3001 var pointer = (cb && cb != '')? ' w3-pointer':''; 3002 var path = 'id-btn-grp-'+ w3int.btn_grp_uniq.toString(); 3003 w3int.btn_grp_uniq++; 3004 cb_param = cb_param || 0; 3005 3006 var font_size = null; 3007 if (isNumber(size) && size >= 0) font_size = px(size); 3008 else 3009 if (isString(size)) font_size = size; 3010 font_size = font_size? (' font-size:'+ font_size +';') : ''; 3011 3012 color = (color && color != '')? (' color:'+ color) : ''; 3013 var onclick = cb? ('onclick="w3int_icon_click(event, '+ sq(path) +', '+ sq(cb) +', '+ sq(cb_param) +')"') : ''; 3014 var p = w3_psa(psa, path + pointer +' fa '+ fa_icon, font_size + color, onclick); 3015 var s = '<i '+ p +'></i>'; 3016 //console.log(s); 3017 return s; 3018} 3019 3020*/ 3021 3022 3023//////////////////////////////// 3024// input 3025//////////////////////////////// 3026 3027// Detect empty lines (only \n) because onchange event not fired in that case. 3028// Also detect and process control character sequences. 3029function w3int_input_keydown(ev, path, cb) 3030{ 3031 var i, s; 3032 var k = ev.key.toUpperCase(); 3033 var ctrl = ev.ctrlKey; 3034 var el = w3_el(path); 3035 if (!el) return; 3036 var input_any_key = w3_contains(el, 'w3-input-any-key'); 3037 var input_any_change = w3_contains(el, 'w3-input-any-change'); 3038 var disabled = w3_parent_with(el, 'w3-disabled'); 3039 //w3_remove(el, 'kiwi-pw'); 3040 var trace = w3_contains(el, 'w3-trace'); 3041 if (trace) console.log('w3int_input_keydown CE k='+ k + (ctrl? ' CTRL ':'') +' val=<'+ el.value +'> cb='+ cb +' disabled='+ disabled); 3042 var cb_a = cb.split('|'); 3043 3044 if (disabled) { 3045 if (trace) console.log('w3int_input_keydown w3-disabled'); 3046 return; 3047 } 3048 3049 if (input_any_key && cb_a[1]) { 3050 //console.log('input_any_key'); 3051 //console.log(cb_a); 3052 //console.log('w3int_input_keydown: input_any_key '+ k +' cb_a[1]='+ cb_a[1]); 3053 //event_dump(ev, "input_any_change", true); 3054 w3_call(cb_a[1], ev, true); 3055 } 3056 3057 if (ev.key == 'Enter') { 3058 if (input_any_change) { 3059 // consider unchanged input value followed by Enter, at the end of input text, to be an input change 3060 if (trace) console.log('w3int_input_keydown: w3-input-any-change + Enter'); 3061 3062 // A newline is going to be added to el.value after w3int_input_keydown() returns.
3063 // If we are currently positioned somewhere in the potential run of newlines at the end of the text 3064 // then remove all the trailing newlines so only a single newline will remain. 3065 s = el.value; 3066 var textLength = el.value.length; // no el.textLength in Safari & Chrome! 3067 if (trace) console.log('newline strip: ss='+ el.selectionStart +' tl='+ textLength); 3068 for (i = el.selectionStart; i < textLength; i++) { 3069 if (s.charAt(i) != '\n') 3070 break; 3071 } 3072 if (trace) console.log('newline strip: i='+ i +' tl='+ textLength); 3073 if (i == textLength) { 3074 while (s.endsWith('\n')) 3075 s = s.slice(0, -1); 3076 if (trace) console.log('s='+ JSON.stringify(s)); 3077 w3_set_value(el, s); // update field so el.selectionStart etc below are also updated 3078 } 3079 3080 if (trace) console.log('ss='+ el.selectionStart +' se='+ el.selectionEnd +' tl='+ textLength); 3081 if ( 3082 // cursor at end of text 3083 (el.selectionStart == el.selectionEnd && el.selectionStart == textLength) || 3084 3085 // all text is selected 3086 (el.selectionStart == 0 && el.selectionEnd == textLength) || 3087 3088 // cause empty input lines followed by Enter to send empty command to shell 3089 (el.value == '') 3090 3091 ) { 3092 if (trace) { 3093 console.log('w3_input_change '+ path +' ...'); 3094 console.log('el.value='+ JSON.stringify(el.value)); 3095 console.log(el); 3096 } 3097 w3_input_change(path, cb, 'kd1', ev); 3098 if (trace) { 3099 console.log('w3_input_change '+ path +'(2)...'); 3100 console.log('el.value='+ JSON.stringify(el.value)); 3101 console.log(el); 3102 } 3103 //w3_dismiss_keyboard(el); 3104 } 3105 } else 3106 if (el.value == '') { 3107 // cause empty input lines followed by Enter to send empty command to shell 3108 if (trace) console.log('w3int_input_keydown: empty line + Enter'); 3109 w3_input_change(path, cb, 'kd2', ev); 3110 w3_dismiss_keyboard(el); 3111 } 3112 } 3113 3114 // if Delete key (Backspace) when entire value is selected then consider it an input change 3115 if (ev.key == 'Backspace' && input_any_change && el.selectionStart == 0 && el.selectionEnd == el.value.length) { 3116 if (trace) console.log('w3int_input_keydown Delete: len='+ el.value.length +' ss='+ el.selectionStart +' se='+ el.selectionEnd); 3117 el.value = ''; 3118 w3_input_change(path, cb, 'kd3', ev); 3119 w3_dismiss_keyboard(el); 3120 } 3121 3122 // prevent dom parents from seeing bubbling event 3123 // Don't call cancelEvent() because that does a stopImmediatePropagation() which prevents the 3124 // subsequent keyup & change/input events from occurring. 3125 ev.stopPropagation(); 3126 3127 if (trace) { 3128 console.log('w3_input_change '+ path +'(3)...'); 3129 console.log('el.value='+ JSON.stringify(el.value)); 3130 console.log(el); 3131 } 3132} 3133 3134function w3int_input_keyup(ev, path, cb) 3135{ 3136 var k = ev.key.toUpperCase(); 3137 var ctrl = ev.ctrlKey; 3138 var el = w3_el(path); 3139 if (!el) return; 3140 3141 /* 3142 if (ev.key == 'Backspace' && el.value == '') 3143 w3_input_change(path, cb, 'ku', ev); 3144 */ 3145 3146 var disabled = w3_parent_with(el, 'w3-disabled'); 3147 var trace = w3_contains(el, 'w3-trace'); 3148 if (trace) console.log('w3int_input_keyup CE k='+ k + (ctrl? ' CTRL ':'') +' path='+ path +' val=<'+ el.value +'> cb='+ cb +' disabled='+ disabled); 3149 3150 var input_any_key = w3_contains(el, 'w3-input-any-key'); 3151 var cb_a = cb.split('|'); 3152 if (input_any_key && cb_a[1]) { 3153 //console.log('input_any_key'); 3154 //console.log(cb_a); 3155 //console.log('w3int_input_keyup: input_any_key '+ k +' cb_a[1]='+ cb_a[1]); 3156 //event_dump(ev, "input_any_change", true); 3157 w3_call(cb_a[1], ev, true); 3158 } 3159 3160 // prevent dom parents from seeing bubbling event 3161 // Don't call cancelEvent() because that does a stopImmediatePropagation() which prevents the 3162 // subsequent keyup & change/input events from occurring. 3163 ev.stopPropagation(); 3164} 3165 3166// event generated from w3_input_force() 3167function w3int_input_process(ev) 3168{ 3169 var a = ev.detail.split('|'); 3170 var path = a[0]; 3171 var cb = a[1]; 3172 //console.log('w3int_input_process', {ev, a, path, cb}); 3173 var el = w3_el(path); 3174 if (el) { 3175 var trace = w3_contains(el, 'w3-trace'); 3176 if (trace) console.log('w3int_input_process path='+ path +' val='+ el.value +' cb='+ cb); 3177 w3int_input_change(ev, path, cb, 'w3int_input_process'); 3178 } 3179} 3180 3181function w3_input_change(path, cb, from, ev) 3182{ 3183 w3int_input_change(ev, path, cb, from); 3184} 3185 3186// 'from' arg (that is appended to w3_call() cb_a[] arg) is: 3187// 'ev' if called from normal onchange event 3188// 'kd' or 'ku' if 'w3-input-any-change' is specified and the "any change" criteria is met 3189// 'in' if 'w3-input-evt' is specified and an input event has been generated 3190function w3int_input_change(ev, path, cb, from) 3191{ 3192 var val = '???'; 3193 var retain = false;
3194 var trace = false; 3195 var el = w3_el(path); 3196 if (el) { 3197 trace = w3_contains(el, 'w3-trace'); 3198 if (trace) console.log('w3int_input_change START path='+ path +' from='+ from); 3199 3200 // cb is a string because can't pass an object to onclick 3201 if (cb) { 3202 var cb_a = cb.split('|'); 3203 if (isArg(from)) cb_a.push(from); 3204 //el.select(); 3205 if (trace) console.log(cb_a); 3206 if (trace) console.log('w3int_input_change CALL '+ cb_a[0] +'('+ sq(path) +', '+ sq(el.value) +', false, cb_a[])'); 3207 retain = w3_call(cb_a[0], path, el.value, /* first */ false, cb_a, ev); 3208 if (from == 'in') return; 3209 } 3210 3211 w3_check_restart_reboot(el); 3212 //w3_check_cfg_reload(el); 3213 if (trace) kiwi_trace(); 3214 w3_schedule_highlight(el); 3215 val = el.value; 3216 3217/* 3218 if (el.value != '' && w3_contains(el, 'w3-encrypted')) { 3219 w3_add(el, 'kiwi-pw'); 3220 el.value = '(encrypted)'; 3221 } 3222*/ 3223 } 3224 3225 w3_dismiss_keyboard(el); 3226 if (trace) console.log('w3int_input_change END path='+ path +' val='+ val +' retain='+ retain +' ev='+ ev); 3227 if (retain === true) { 3228 w3_field_select(el, {mobile:1}); // retain field selection 3229 } else { 3230 w3int_post_action(); 3231 } 3232 3233 // prevent dom parents from seeing bubbling event 3234 // Don't call cancelEvent() because that does a stopImmediatePropagation() which prevents the 3235 // subsequent keyup & change/input events from occurring. 3236 if (isArg(ev)) ev.stopPropagation(); 3237} 3238 3239function w3int_input_up_down_cb(path, cb_param, first, ev) 3240{ 3241 //event_dump(ev, path, 1); 3242 if (ev.type != 'click' && ev.type != 'touch') return; 3243 var cb_a = cb_param.split('|'); 3244 var delta = +cb_a[0]; 3245 var id_s = cb_a[1]; 3246 var cb_s = cb_a[2]; 3247 var val = +w3_el(id_s).value + delta; 3248 //console.log('w3int_input_up_down_cb', {cb_param, ev}); 3249 w3_input_force(id_s, cb_s, val.toFixed(0)); 3250} 3251 3252// No cb_param here because w3int_input_change() passes the input value as the callback parameter. 3253// But can specify a composite callback name (i.e. "cb|param0|param1") that is passed to callback routine. 3254// 3255// NB: using w3_esc_dq(val) below eliminates the need to call admin_set_decoded_value() via 3256// admin tab *_focus() routines solely to work around the escaping of double quotes in the 3257// val issue. 3258 3259function w3_input(psa, label, path, val, cb, placeholder) 3260{ 3261 var dump = psa.includes('w3-dump'); 3262 if (dump) console.log('w3_input()...'); 3263 var id = w3_add_id(path); 3264 cb = cb || ''; 3265 var phold = placeholder? ('placeholder="'+ placeholder +'"') : ''; 3266 var custom = psa.includes('w3-custom-events'); 3267 var onchange = ' onchange="w3int_input_change(event, '+ sq(path) +', '+ sq(cb) +', '+ sq('ev') +')"'; 3268 var oninput = psa.includes('w3-input-evt')? (' oninput="w3int_input_change(event, '+ sq(path) +', '+ sq(cb) +', '+ sq('in') +')"') : ''; 3269 var onkeydown = ' onkeydown="w3int_input_keydown(event, '+ sq(path) +', '+ sq(cb) +')"'; 3270 var onkeyup = ' onkeyup="w3int_input_keyup(event, '+ sq(path) +', '+ sq(cb) +')"'; 3271 var events = (path && !custom)? (onchange + oninput + onkeydown + onkeyup) : ''; 3272 //var auto = psa.includes('w3-no-auto-anything')? ' autocomplete="off" autocorrect="off" autocapitalize="off"' : ''; 3273 var auto = ' autocomplete="off" autocorrect="off" autocapitalize="off"'; 3274 val = ' value='+ dq(w3_esc_dq(val) || ''); 3275 var inline = psa.includes('w3-label-inline'); 3276 var bold = !psa.includes('w3-label-not-bold'); 3277 3278 var label_right = ''; 3279 if (isArray(label)) { 3280 var a = label; 3281 label = a[0] || ''; 3282 label_right = a[1] || ''; 3283 } 3284 label = label || ''; 3285 var label_spacing = (label != '' && inline)? 'w3int-margin-input' : ''; 3286 3287 // type="password" is no good because it forces the submit to be https which we don't support 3288 //var type = 'type='+ (psa.includes('w3-password')? '"password"' : '"text"'); 3289 3290 var psa3 = w3_psa3(psa); 3291 if (dump) { 3292 console.log(psa); 3293 console.log(psa3); 3294 } 3295 var psa_outer = w3_psa(psa3.left, inline? 'w3-inline-flex':''); 3296 var psa_label = w3_psa_mix(psa3.middle, (label != '' && bold)? 'w3-bold':''); 3297 var style = psa.includes('w3-no-styling')? '' : 'w3-input w3-border w3-hover-shadow'; 3298 var type = psa3.right.includes('type=')? '' : 'type="text"'; 3299 var psa_inner = w3_psa(psa3.right, w3_sb(id, style, label_spacing), '', w3_sb(type, phold)); 3300 if (dump) console.log('O:['+ psa_outer +'] L:['+ psa_label +'] I:['+ psa_inner +']'); 3301
3302 // NB: include id in an id= for benefit of keyboard shortcut field detection 3303 var in_s = '<input id='+ dq(id) +' '+ psa_inner + val + events + auto +'>'; 3304 var cb_s = '|'+ id +'|'+ cb; 3305 3306 if (psa.includes('w3-up-down')) { 3307 in_s = w3_inline('', 3308 w3_div('w3-flex-col w3-margin-R-4', 3309 w3_icon('w3-momentary w3-pointer w3-margin-B-1||title="up"', 'fa-arrow-up', 15, 'orange', 'w3int_input_up_down_cb', '1'+ cb_s), 3310 w3_icon('w3-momentary w3-pointer w3-margin-T-1||title="down"', 'fa-arrow-down', 15, 'cyan', 'w3int_input_up_down_cb', '-1'+ cb_s) 3311 ), 3312 in_s 3313 ); 3314 } 3315 3316 var s = 3317 '<div '+ psa_outer +'>' + 3318 w3int_label(psa_label, label, path) + 3319 in_s + 3320 label_right + 3321 '</div>'; 3322 if (dump) console.log(s); 3323 //w3int_input_set_id(id); 3324 return s; 3325} 3326 3327function w3_input_force(path, cb, input) 3328{ 3329 //console.log('w3_input_force', {path, cb, input}); 3330 var el = w3_el(path); 3331 if (!el) return; 3332 el.addEventListener('input', w3int_input_process, w3.CAPTURING); 3333 el.value = input.replace('&vbar;', '|'); 3334 el.dispatchEvent(new CustomEvent('input', { detail: path +'|'+ cb })); 3335 el.removeEventListener('input', w3int_input_process, w3.CAPTURING); 3336} 3337 3338/* 3339function w3int_input_set_id(id) 3340{ 3341 console.log('### w3int_input_set_id el='+ id +' DO NOT USE'); 3342 return; 3343 //if (id == '') return; 3344 //setTimeout(function() { w3int_input_set_id_timeout(id); }, 3000); 3345} 3346 3347function w3int_input_set_id_timeout(id) 3348{ 3349 var el = w3_el(id); 3350 //console.log('### w3int_input_set_id_timeout el='+ id +' CONSIDER'); 3351 if (!el) return; 3352 if (el.id != '') { 3353 console.log('### w3int_input_set_id_timeout el='+ id +' id='+ id +' ALREADY SET?'); 3354 return; 3355 } 3356 w3_iterate_classList(el, function(className, idx) { 3357 if (className.startsWith('id-')) { 3358 console.log('### w3int_input_set_id_timeout el='+ id +' id='+ id +' SET --------'); 3359 el.id = className; 3360 } 3361 }); 3362} 3363*/ 3364 3365// used when current value should come from config param 3366function w3_input_get(psa, label, path, cb, init_val, placeholder) 3367{ 3368 // FIXME 3369 //var update_path_var = psa.includes('w3-update'); 3370 var save = psa.includes('w3-defer')? EXT_SAVE_DEFER : undefined; 3371 var cur_val = ext_get_cfg_param(path, (init_val == undefined)? null : init_val, save); 3372 3373 // NB: This is still needed because legacy params might still have URI encoding (i.e. %NN) 3374 // whereas our newer model that stores json does not. But kiwi_decodeURIComponent() will 3375 // correct e.g. any singlet '%' saved in a json param. 3376 cur_val = kiwi_decodeURIComponent('w3_input_get:'+ path, cur_val); 3377 //console.log('w3_input_get: path='+ path +' cur_val="'+ cur_val +'" placeholder="'+ placeholder +'"'); 3378 return w3_input(psa, label, path, cur_val, cb, placeholder); 3379} 3380 3381 3382//////////////////////////////// 3383// textarea 3384//////////////////////////////// 3385 3386// The test for 'w3-input-any-change' that suppresses inclusion of the 'onchange=' event handler 3387// fixes a strange problem we noticed: With the 'onchange=' the actual onchange event is long-delayed 3388// until the first unrelated click completely outside the textarea! This ends up causing double calls 3389// to w3int_input_change(). The first from w3int_input_keydown() due to w3-input-any-change and the second 3390// from the delayed change event. 3391// 3392// The problem doesn't seem to occur when w3-input-any-change is used with w3_input() 3393 3394function w3_textarea(psa, label, path, val, rows, cols, cb) 3395{ 3396 var id = w3_add_id(path); 3397 var spacing = (label != '')? ' w3-margin-T-8' : ''; 3398 var custom = psa.includes('w3-custom-events'); 3399 var onchange = psa.includes('w3-input-any-change')? '' : (' onchange="w3int_input_change(event, '+ sq(path) +', '+ sq(cb) +')"'); 3400 var onkeydown = ' onkeydown="w3int_input_keydown(event, '+ sq(path) +', '+ sq(cb) +')"'; 3401 var onkeyup = ' onkeyup="w3int_input_keyup(event, '+ sq(path) +', '+ sq(cb) +')"'; 3402 var events = (path && !custom)? (onchange + onkeydown + onkeyup) : ''; 3403 var bold = !psa.includes('w3-label-not-bold'); 3404 var psa3 = w3_psa3(psa); 3405 var psa_label = w3_psa_mix(psa3.middle, (label != '' && bold)? 'w3-bold':''); 3406 var psa_inner = w3_psa(psa3.right, 'w3-input w3-border w3-hover-shadow '+ id + spacing, '', 'rows='+ dq(rows) +' c
3406ols='+ dq(cols)); 3407 val = val || ''; 3408 3409 var s = 3410 w3_div(psa3.left, 3411 w3int_label(psa_label, label, path) + 3412 '<textarea '+ psa_inner + events +'>'+ val +'</textarea>' 3413 ); 3414 //if (psa.includes('w3-dump')) console.log('$'); 3415 //if (psa.includes('w3-dump')) console.log(psa3); 3416 //if (psa.includes('w3-dump')) console.log(psa_inner); 3417 //if (psa.includes('w3-dump')) console.log('label='+ label); 3418 //if (psa.includes('w3-dump')) console.log('val='+ val); 3419 if (psa.includes('w3-dump')) console.log('s='+ s); 3420 return s; 3421} 3422 3423// used when current value should come from config param 3424function w3_textarea_get_param(psa, label, path, rows, cols, cb, init_val) 3425{ 3426 // FIXME 3427 //var update_path_var = psa.includes('w3-update'); 3428 var save = psa.includes('w3-defer')? EXT_SAVE_DEFER : undefined; 3429 var cur_val = ext_get_cfg_param(path, (init_val == undefined)? null : init_val, save); 3430 3431 // NB: This is still needed because legacy params might still have URI encoding (i.e. %NN) 3432 // whereas our newer model that stores json does not. But kiwi_decodeURIComponent() will 3433 // correct e.g. any singlet '%' saved in a json param. 3434 cur_val = kiwi_decodeURIComponent('w3_textarea_get_param:'+ path, cur_val); 3435 //if (psa.includes('w3-dump')) console.log('w3_textarea_get_param: path='+ path +' cur_val="'+ cur_val +'"'); 3436 return w3_textarea(psa, label, path, cur_val, rows, cols, cb); 3437} 3438 3439 3440//////////////////////////////// 3441// alert 3442//////////////////////////////// 3443 3444function w3_alert(id, psa, msg, close, opts) { 3445 path = 'id-alert-'+ id +'-'+ w3int.alert_seq++; 3446 opts = opts || {}; 3447 if (!opts.no_cancel) w3_alert_cancel(); 3448 var width = opts.width || '650px'; 3449 var top = opts.top || '50%'; 3450 var left = opts.left || '50%'; 3451 // translate[XY](-50%) so e.g. top:50% left:50% are referenced to div center 3452 var css = sprintf( 3453 'position: fixed; top: %s; left: %s; transform: translateX(-50%%) translateY(-50%%); width: %s; ' + 3454 'background: #444; color: white; padding: 15px 25px; border-radius: 8px; ' + 3455 'box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 9999; font-family: sans-serif;', 3456 top, left, width); 3457 var psa3 = w3_psa3(psa); 3458 var psa_outer = w3_psa(psa3.middle, path, css); 3459 var psa_inner = w3_psa(psa3.right); 3460 //console.info(psa_outer); 3461 //console.info(psa_inner); 3462 var html = 3463 w3_div(psa_outer, 3464 w3_text('w3-wrap '+ psa_inner, msg), 3465 opts.fadeout? '' : w3_button('w3-margin-T-16 w3-green', 'Close', 'w3int_alert_ok', path) 3466 ); 3467 var alert = w3_create_appendElement('id-kiwi-body', 'div', html, path); 3468 w3int.alert = { path:path, active:true, close:close }; 3469 if (opts.fadeout) { 3470 setTimeout(function() { w3_alert_close(); }, opts.fadeout); 3471 } 3472} 3473 3474function w3int_alert_ok(path, cb_param) { 3475 //console.log('w3int_alert_ok: path_id='+ w3int.alert.path +' path='+ path +' cb_param='+ cb_param); 3476 if (!w3_alert_active()) return; 3477 var alert = w3_el(cb_param); 3478 alert.style.transition = 'opacity 0.5s'; 3479 alert.style.opacity = '0'; 3480 setTimeout(function() { alert.remove(); }, 500); 3481 var close = w3int.alert.close; 3482 w3int.alert = {}; // in case close routine does another alert 3483 w3_call(close); 3484} 3485 3486function w3_alert_active() { return (w3int.alert.active == true); } 3487 3488function w3_alert_cancel() { 3489 if (w3_alert_active()) { 3490 w3int.alert.close = null; 3491 w3int_alert_ok(null, w3int.alert.path); 3492 } 3493} 3494 3495function w3_alert_close() { 3496 if (w3_alert_active()) { 3497 w3int_alert_ok(null, w3int.alert.path); 3498 } 3499} 3500 3501 3502//////////////////////////////// 3503// checkbox 3504//////////////////////////////// 3505 3506function w3int_checkbox_change(path, cb, cb_param) 3507{ 3508 var el = w3_el(path); 3509 w3_check_restart_reboot(el); 3510 3511 // cb is a string because can't pass an object to onclick 3512 if (cb) { 3513 //console.log('w3int_checkbox_change el='+ el +' checked='+ el.checked); 3514 //el.select(); 3515 w3_schedule_highlight(el); 3516 w3_call(cb, path, el.checked, /* first */ false, cb_param); 3517 } 3518 3519 w3int_post_action(); 3520} 3521 3522function w3_checkbox(psa, label, path, checked, cb, cb_param) 3523{ 3524 var dump = psa.includes('w3-dump'); 3525 var id = w3_add_id(path); 3526 var onchange = ' onchange="w3int_checkbox_change('+ sq(path) +', '+ sq(cb) +', '+ sq(cb_param) +')"'; 3527 var checked_s = checked? ' checked' : ''; 3528 var inline = psa.includes('w3-label-inline'); 3529 var left = psa.includes('w3-label-left'); 3530 var bold = !psa.includes('w3-label-not-bold'); 3531 var spacing = (label != '' && inline)? (left? ' w3-margin-L-8' : ' w3-margin-R-8') : ''; 3532 3533 var psa3 = w3_psa3(psa); 3534 var psa_outer = w3_psa(psa3.left, inline? 'w3-inline-flex':''); 3535 var psa_label = w3_psa_mix(psa3.middle, (label != '' && bold)? 'w3-bold':''); 3536 var psa_inner = w3_psa(psa3.right, 'w3-input w3-width-auto w3-border w3-pointer w3-hover-shadow '+ id + spacing, '', 'type="checkbox"'); 3537 3538 var ls = w3int_label(psa_label, label, path); 3539 var cs = '<input '+ psa_inner + checked_s + onchange +'>'; 3540 var s = 3541 '<div '+ psa_outer +'>' + 3542 (left? (ls + cs) : (cs + ls)) + 3543 '</div>'; 3544 3545 // run the callback after instantiation with the initial control value 3546 if (cb) 3547 setTimeout(function() { 3548 //console.log('w3_checkbox: initial callback: '+ cb +'('+ sq(path) +', '+ checked +')'); 3549 w3_call(cb, path, checked, /* first */ true, cb_param); 3550 }, 500); 3551 3552 if (dump) console.log(s); 3553 return s; 3554} 3555 3556// used when current value should come from config param 3557function w3_checkbox_get_param(psa, label, path, cb, init_val, cb_param) 3558{ 3559 var update_path_var = psa.includes('w3-update');
3560 var save = psa.includes('w3-defer')? EXT_SAVE_DEFER : undefined; 3561 var cur_val = ext_get_cfg_param(path, (init_val == undefined)? null : init_val, save, update_path_var); 3562 //console.log('w3_checkbox_get_param: path='+ path +' update_path_var='+ update_path_var +' cur_val='+ cur_val +' init_val='+ init_val); 3563 return w3_checkbox(psa, label, path, cur_val, cb, cb_param); 3564} 3565 3566function w3_checkbox_get(path) 3567{ 3568 var el = w3_el(path); 3569 if (!el) return; 3570 return (el.checked? true:false); 3571} 3572 3573function w3_checkbox_set(path, checked) 3574{ 3575 var el = w3_el(path); 3576 //console.log('w3_checkbox_set path='+ path +' checked='+ checked +' el=...'); 3577 //console.log(el); 3578 if (!el) return; 3579 el.checked = checked? true:false; 3580} 3581 3582 3583//////////////////////////////// 3584// select (HTML menu) 3585//////////////////////////////// 3586 3587var W3_SELECT_SHOW_TITLE = -1; 3588 3589// Event behavior of w3_select() i.e. HTML <select>|<option> elements 3590// menu open: MD focus 3591// change menu item: MU blur change click 3592// same menu item: MD MU w3_click-tgt=OPTION-BLUR blur FSEL click 3593// when menu popup doesn't cover cursor: MU w3_click-tgt=SELECT blur FSEL click 3594// 3595// Since change+blur events are not fired if the same menu item is clicked 3596// must do blur here on click event. But only if same selected item is detected 3597// by ev.target being an <option> element. 3598 3599function w3int_select_click(ev, path, cb, cb_param) 3600{ 3601 // ev.target = SELECT|OPTION, ev.currentTarget = SELECT 3602 //console.log('w3int_select_click: '+ w3_id(ev.currentTarget)); 3603 var el = ev.target; 3604 if (el && el.nodeName && el.nodeName == 'OPTION') { 3605 //console.log('w3int_select_click: same menu item clicked: BLUR and FSEL'); 3606 el.blur(); 3607 w3int_post_action(); 3608 } 3609} 3610 3611function w3int_select_change(ev, path, cb, cb_param) 3612{ 3613 // ev.target = SELECT|OPTION, ev.currentTarget = SELECT 3614 //console.log('w3int_select_change: CHANGE '+ w3_id(ev.currentTarget)); 3615 var el = ev.currentTarget; 3616 w3_check_restart_reboot(el); 3617 3618 // cb is a string because can't pass an object to onclick 3619 if (cb) { 3620 w3_call(cb, path, el.value, /* first */ false, cb_param); 3621 } 3622 3623 w3int_post_action(); 3624} 3625 3626function w3int_select(psa, label, title, path, sel, opts_s, cb, cb_param) 3627{ 3628 var id = w3_add_id(path); 3629 var first = ''; 3630 3631 if (title != '') { 3632 var as = title.split('<br>'); 3633 as.forEach(function(s,i) { 3634 first += '<option value="-1" '+ ((sel == W3_SELECT_SHOW_TITLE && i == 0)? 'selected':'') +' disabled>' + s +'</option>'; 3635 }); 3636 } else { 3637 if (sel == W3_SELECT_SHOW_TITLE) sel = 0; 3638 } 3639 3640 var inline = psa.includes('w3-label-inline'); 3641 var bold = !psa.includes('w3-label-not-bold'); 3642 var spacing = (label != '' && !inline && !psa.includes('w3-margin-T-'))? 'w3-margin-T-8' : ''; 3643 if (inline) spacing += 'w3-margin-left'; 3644 if (cb == undefined) cb = ''; 3645 var onchange = 'onchange="w3int_select_change(event, '+ sq(path) +', '+ sq(cb) +', '+ sq(cb_param) +')"'; 3646 var onclick = 'onclick="w3int_select_click(event, '+ sq(path) +', '+ sq(cb) +', '+ sq(cb_param) +')"'; 3647 3648 var psa3 = w3_psa3(psa); 3649 var psa_outer = w3_psa(psa3.left, inline? 'w3-inline-flex':''); 3650 var psa_label = w3_psa_mix(psa3.middle, (label != '' && bold)? 'w3-bold':''); 3651 var psa_inner = w3_psa(psa3.right, w3_sb(id, 'w3-select-menu', spacing), '', onchange +' '+ onclick); 3652 3653 var s = 3654 '<div '+ psa_outer +'>' + 3655 w3int_label(psa_label, label, path) + 3656 // need <br> because both <label> and <select> are inline elements 3657 ((!inline && label != '')? '<br>':'') + 3658 '<select '+ psa_inner +'>'+ first + opts_s +'</select>' + 3659 '</div>'; 3660 3661 // run the callback after instantiation with the initial control value 3662 if (cb && sel != W3_SELECT_SHOW_TITLE) 3663 setTimeout(function() { 3664 //console.log('w3_select: initial callback: '+ cb +'('+ sq(path) +', '+ sel +')'); 3665 w3_call(cb, path, sel, /* first */ true, cb_param); 3666 }, 500); 3667 3668 if (psa.includes('w3-dump')) console.log(s); 3669 return s; 3670} 3671 3672function w3int_select_options(sel, opts, show_empty) 3673{ 3674 var s = ''; 3675 3676 // 'min:max' 3677 // range of integers (increment one assumed) 3678 if (isString(opts)) { 3679 var rng = opts.split(':'); 3680 if (rng.length == 2) { 3681 var min = +rng[0]; 3682 var max = +rng[1]; 3683 var idx = 0; 3684 for (var i = min; i <= max; i++) { 3685 s += '<option value='+ dq(idx) +' '+ ((idx == sel)? 'selected':'') +'>'+ i +'</option>'; 3686 idx++; 3687 } 3688 } 3689 } else 3690 3691 // [ n, n ... ] 3692 // [ 's', 's', n, 's' ... ] 3693 // [ 's', n, { first_key:text, [name:text,] key:x ... }, n ... ]
3694 // mixed array of strings, numbers or objects 3695 // if object, take first object element's value as menu option text 3696 // *unless* value obj.name exists, then obj.name is menu option text 3697 if (isArray(opts)) { 3698 opts.forEach(function(obj, i) { 3699 if (obj == null) return; 3700 if (isObject(obj)) { 3701 //var keys = Object.keys(obj); 3702 //obj = obj[keys[0]]; 3703 var hasName = isDefined(obj.name); 3704 if (hasName && !show_empty && obj.name == '') return; 3705 obj = hasName? obj.name : w3_obj_seq_el(obj, 0); 3706 } 3707 s += '<option value='+ dq(i) +' '+ ((i == sel)? 'selected':'') +'>'+ obj +'</option>'; 3708 }); 3709 } else 3710 3711 // { key0:text0, key1:text1, obj_text:{ [name:text] [value:i] } ... } 3712 // object: enumerate sequentially like an array. 3713 // object elements 3714 // non-object: (number, string, etc.) 3715 // use element value as menu option text 3716 // option value is element's sequential position 3717 // object: 3718 // key itself is menu option text 3719 // *unless* value obj.name exists, then obj.name is menu option text 3720 // option value is elements's sequential position 3721 // *unless* value obj.value exists, then obj.value is option value 3722 if (isObject(opts)) { 3723 if (opts == null) return ''; 3724 w3_obj_enum(opts, function(key, i, obj) { 3725 var value, text, disabled = false; 3726 if (isObject(obj)) { 3727 value = isDefined(obj.value)? obj.value : i; 3728 var hasName = isDefined(obj.name); 3729 if (hasName && !show_empty && obj.name == '') return; 3730 text = hasName? obj.name : key; 3731 if (isDefined(obj.disabled) && obj.disabled) disabled = true; 3732 } else { 3733 value = i; 3734 text = obj; 3735 } 3736 s += '<option value='+ dq(value) + ((i == sel)? ' selected':'') + (disabled? ' disabled':'') +'>'+ text +'</option>\n'; 3737 }); 3738 } 3739 3740 // If sel is null, e.g. as a result of a failing match from w3_obj_key_seq(), 3741 // set menu to a disabled entry named "UNKNOWN". 3742 if (sel == null) { 3743 s += '<option value=-1 selected disabled>(unknown)</option>\n'; 3744 } 3745 3746 return s; 3747} 3748 3749function w3_select(psa, label, title, path, sel, opts, cb, cb_param) 3750{ 3751 var s = w3int_select_options(sel, opts, psa.includes('w3-show-empty')); 3752 if (psa.includes('w3-dump')) console.log(s); 3753 return w3int_select(psa, label, title, path, sel, s, cb, cb_param); 3754} 3755 3756// hierarchical -- menu entries interspersed with disabled (non-selectable) headers 3757// { "key0":[fv0, fv1 ...], "key1":[fv0, fv1 ...] ... } 3758// object: enumerate sequentially like an array using: 3759// object keys as the disabled menu entries (string only, numeric or empty-string keys ignored) 3760// '_' in key => menu header line break 3761// arrays as the menu options 3762// array elements are w3_first_value()'s e.g. int, string, first array value etc. 3763function w3int_select_hier(psa, label, title, path, sel, collapse, opts, cb, cb_param) 3764{ 3765 var s = ''; 3766 var idx = 0; 3767 if (!isObject(opts)) return; 3768 3769 w3_obj_enum(opts, function(key, i, a) { 3770 if (key != '' && !isNumber(+key)) { 3771 as = key.split('_'); // '_' => menu header line break 3772 as.forEach(function(e) { 3773 if (!e || e != '') 3774 s += '<option value='+ dq(idx++) +' disabled>'+ e +'</option> '; 3775 }); 3776 } 3777 if (!isArray(a)) return; 3778 3779 a.forEach(function(el, j) { 3780 if (collapse && j >= collapse) return; // only show first collapse number of entries 3781 var v = w3_first_value(el); 3782 if (v == 'null') return; 3783 var v2 = w3_second_value(el); 3784 var _class = v2? (' class='+ dq(v2)) : ''; 3785 s += '<option value='+ dq(idx++) +' id="id-'+ i +'-'+ j +'"' + _class +'>'+ v.toString() +'</option> '; 3786 }); 3787 }); 3788 3789 /* 3790 var keys = Object.keys(opts); 3791 for (var i=0; i < keys.length; i++) { 3792 var key = keys[i]; 3793 s += '<option value='+ dq(idx++) +' disabled>'+ key +'</option> '; 3794 var a = opts[key]; 3795 if (!isArray(a)) continue; 3796 3797 for (var j=0; j < a.length; j++) { 3798 var v = w3_first_value(a[j]); 3799 s += '<option value='+ dq(idx++) +' id="id-'+ i +'-'+ j +'">'+ v.toString() +'</option> '; 3800 } 3801 } 3802 */ 3803 3804 return w3int_select(psa, label, title, path, sel, s, cb, cb_param); 3805} 3806 3807function w3_select_hier(psa, label, title, path, sel, opts, cb, cb_param) 3808{ 3809 return w3int_select_hier(psa, label, title, path, sel, 0, opts, cb, cb_param); 3810} 3811 3812function w3_select_hier_collapse(psa, label, title, path, sel, collapse, opts, cb, cb_param) 3813{ 3814 return w3int_select_hier(psa, label, title, path, sel, collapse, opts, cb, cb_param); 3815} 3816 3817// conditional -- individual menu entries can be enabled/disabled 3818// [ ['s', 1|0], ... ] 3819function w3_select_conditional(psa, label, title, path, sel, opts, cb, cb_param) 3820{ 3821 var s = ''; 3822 var idx = 0; 3823 if (!isArray(opts)) return; 3824
3825 opts.forEach(function(el) { 3826 if (isArray(el)) 3827 s += '<option value='+ dq(idx) +' '+ ((idx == sel)? 'selected':'') +' '+ (el[1]? '':'disabled') +'>'+ el[0] +'</option> '; 3828 idx++; 3829 }); 3830 3831 return w3int_select(psa, label, title, path, sel, s, cb, cb_param); 3832} 3833 3834function w3_select_set_disabled(path, value, disabled, title) 3835{ 3836 w3_iterate_children(path, function(el, i) { 3837 //console.log('w3_select_set_disabled['+i+']'); 3838 //console.log(el); 3839 if (el.value == value) { 3840 el.disabled = disabled? true:false; 3841 if (isArg(title)) el.title = title; 3842 } 3843 }); 3844} 3845 3846// used when current value should come from config param 3847function w3_select_get_param(psa, label, title, path, opts, cb, init_val, cb_param) 3848{ 3849 var update_path_var = psa.includes('w3-update'); 3850 var save = psa.includes('w3-defer')? EXT_SAVE_DEFER : undefined; 3851 var cur_val = ext_get_cfg_param(path, (init_val == undefined)? 0 : init_val, save, update_path_var); 3852 return w3_select(psa, label, title, path, cur_val, opts, cb, cb_param); 3853} 3854 3855function w3_select_enum(path, func) 3856{ 3857 path = w3_el(path); 3858 if (!path) return; 3859 w3_iterate_children(path, func); 3860} 3861 3862function w3_select_match(path, s) 3863{ 3864 var found = false, idx = -1; 3865 w3_select_enum(path, 3866 function(e) { 3867 if (!found && !e.disabled && e.innerHTML.toLowerCase().startsWith(s)) { 3868 idx = e.value; 3869 found = true; 3870 } 3871 } 3872 ); 3873 return { found:found, idx:idx }; 3874} 3875 3876function w3_select_get_value(path, idx) 3877{ 3878 var found = null, last_disabled = null; 3879 w3_select_enum(path, 3880 function(e) { 3881 if (e.value == idx) 3882 found = e.innerHTML; 3883 if (!found && e.disabled) 3884 last_disabled = e.innerHTML; 3885 } 3886 ); 3887 return { option:found, last_disabled:last_disabled }; 3888} 3889 3890function w3_select_value(path, idx, opt) 3891{ 3892 if (w3_opt(opt, 'all')) { 3893 //console.log('{all:1}'); 3894 w3_els(path, function(el) { 3895 //console.log(el); 3896 el.value = idx; 3897 }); 3898 } else { 3899 var el = w3_el(path); 3900 if (!el) return; 3901 el.value = idx; 3902 } 3903} 3904 3905function w3_select_set_if_includes(path, s, opt) 3906{ 3907 var found = false; 3908 var re = RegExp(s); 3909 w3_select_enum(path, function(option) { 3910 //console.log('w3_select_set_if_includes s=['+ s +'] consider=['+ option.innerHTML +']'); 3911 if (re.test(option.innerHTML)) { 3912 if (!found) { 3913 w3_select_value(path, option.value); 3914 if (w3_opt(opt, 'cb')) { 3915 w3_call(opt.cb, path, option.value); 3916 } 3917 //console.log('w3_select_set_if_includes TRUE value='+ option.value); 3918 found = true; 3919 } 3920 } 3921 }); 3922 return found; 3923} 3924 3925 3926//////////////////////////////// 3927// slider 3928//////////////////////////////// 3929 3930function w3int_slider_change(ev, complete, path, cb, cb_param) 3931{ 3932 var el = ev.currentTarget; 3933 w3_check_restart_reboot(el); 3934 3935 // cb is a string because can't pass an object to onclick 3936 if (cb) { 3937 //console.log('w3int_slider_change path='+ path +' el.value='+ el.value); 3938 w3_call(cb, path, el.value, complete, /* first */ false, cb_param); 3939 } 3940 3941 if (complete) { 3942 //console.log('w3int_slider_change COMPLETE path='+ path +' el.value='+ el.value); 3943 w3int_post_action(); 3944 } 3945} 3946 3947function w3int_slider_wheel(evt, path, cb, cb_param, need_shift_s) 3948{ 3949 var need_shift = +need_shift_s; 3950 w3int.rate_limit_evt[cb] = evt; 3951 w3int.rate_limit_need_shift[cb] = need_shift; 3952 w3int.rate_limit[cb](cb_param); 3953 //console.log(cb +' need_shift='+ need_shift +' shiftKey='+ evt.shiftKey); 3954 if (!need_shift || evt.shiftKey) 3955 evt.preventDefault(); 3956} 3957 3958function w3_slider_wheel(cb, el, cur, slow, fast) 3959{ 3960 el = w3_el(el); 3961 if (!el) return null; 3962 var evt = w3int.rate_limit_evt[cb]; 3963 //event_dump(evt, cb, 1); 3964 //console.log(cb +' need_shift='+ w3int.rate_limit_need_shift[cb] +' shiftKey='+ evt.shiftKey +' button='+ evt.button +' buttons='+ evt.buttons); 3965 if (w3int.rate_limit_need_shift[cb] && !evt.shiftKey) 3966 return null; 3967 var x = evt.deltaX; 3968 var y = evt.deltaY; 3969 var ay = Math.abs(y); 3970 var up_down = ((x < 0 && y <= 0) || (x >= 0 && y < 0)); 3971 //console.log(x +' '+ y +' '+ (up_down? 'U':'D') +' '+ w3_id(evt.target)); 3972 up_down = up_down? 1 : -1; 3973 var speed = up_down * ((ay < 50)? slow : fast); 3974 //console.log('y='+ y +' '+ speed); 3975 var nval = cur + speed; 3976 //console.log('w3_wheel '+ cb +' min='+ el.min +' max='+ el.max +' step='+ el.step +' cur='+ cur +' speed='+ speed +' nval='+ nval); 3977 return w3_clamp(nval, +el.min, +el.max); 3978} 3979 3980//function w3int_slider_mobile(evt) { evt.preventDefault(); } 3981 3982function w3_slider(psa, label, path, val, min, max, step, cb, cb_param) 3983{
3984 var dump = psa.includes('w3-dump'); 3985 var id = w3_add_id(path); 3986 var inline = psa.includes('w3-label-inline'); 3987 var bold = !psa.includes('w3-label-not-bold'); 3988 var spacing = (label != '' && inline)? 'w3-margin-L-8' : ''; 3989 if (inline) spacing += ' w3-margin-left'; 3990 3991 value = (val == null)? '' : w3_strip_quotes(val); 3992 value = 'value='+ dq(value); 3993 cb_param = cb_param || 0; 3994 var oc = ' oninput="w3int_slider_change(event, 0, '+ sq(path) +', '+ sq(cb) +', '+ sq(cb_param) +')"'; 3995 // change event fires when the slider is done moving 3996 var os = ' onchange="w3int_slider_change(event, 1, '+ sq(path) +', '+ sq(cb) +', '+ sq(cb_param) +')"'; 3997 //var om = kiwi_isMobile()? ' ontouchstart="w3int_slider_mobile(event)"' : ''; 3998 3999 var ow = ''; 4000 if (psa.includes('w3-wheel')) { 4001 var need_shift = psa.includes('w3-wheel-shift')? 1:0; 4002 var cb_wheel = removeEnding(cb, '_cb') + '_wheel_cb'; 4003 ow = ' onwheel="w3int_slider_wheel(event, '+ sq(path) +', '+ sq(cb_wheel) +', '+ sq(cb_param) +', '+ sq(need_shift) +')"'; 4004 w3int.rate_limit[cb_wheel] = kiwi_rateLimit(cb_wheel, 170 /* msec */); 4005 } 4006 4007 var psa3 = w3_psa3(psa); 4008 var psa_outer = w3_psa(psa3.left, inline? 'w3-inline-flex':''); 4009 var psa_label = w3_psa_mix(psa3.middle, (label != '' && bold)? 'w3-bold':''); 4010 var psa_inner = w3_psa(psa3.right, w3_sb(id, spacing), '', value + 4011 ' type="range" min='+ dq(min) +' max='+ dq(max) +' step='+ dq(step) + oc + os + ow); 4012 4013 var s = (label != '') ? 4014 ('<div '+ psa_outer +'>' + 4015 w3int_label(psa_label, label, path) + 4016 // need <br> because both <label> and <input-range> are inline elements 4017 ((!inline && label != '')? '<br>':'') + 4018 '<input '+ psa_inner +'>' + 4019 '</div>') 4020 : 4021 ('<input '+ psa_inner +'>'); 4022 4023 // run the callback after instantiation with the initial control value 4024 if (cb) 4025 setTimeout(function() { 4026 //console.log('w3_slider: initial callback: '+ cb +'('+ sq(path) +', '+ val +')'); 4027 w3_call(cb, path, val, /* complete */ true, /* first */ true, cb_param); 4028 }, 500); 4029 4030 if (dump) console.log(s); 4031 return s; 4032} 4033 4034function w3_slider_set(path, val, cb) 4035{ 4036 w3_set_value(path, val); 4037 w3_call(cb, path, val, /* complete */ true, /* first */ false); 4038} 4039 4040function w3_slider_setup(path, min, max, step, val) 4041{ 4042 var el = w3_el(path); 4043 if (!el) return el; 4044 //console.log('w3_slider_setup path='+ path +' min='+ min +' max='+ max +' step='+ step +' val='+ val); 4045 el.min = min; 4046 el.max = max; 4047 el.step = step; 4048 el.value = val; 4049 return el; 4050} 4051 4052// used when current value should come from config param 4053function w3_slider_get_param(psa, label, path, min, max, step, cb, cb_param, init_val) 4054{ 4055 var update_path_var = psa.includes('w3-update'); 4056 var save = psa.includes('w3-defer')? EXT_SAVE_DEFER : undefined; 4057 var cur_val = ext_get_cfg_param(path, (init_val == undefined)? 0 : init_val, save, update_path_var); 4058 //console.log('w3_slider_get_param path='+ path +' update_path_var='+ update_path_var +' init_val='+ init_val +' cur_val='+ cur_val +' psa='+ psa); 4059 return w3_slider(psa, label, path, cur_val, min, max, step, cb, cb_param); 4060} 4061 4062 4063//////////////////////////////// 4064// menu (Kiwi menu) 4065//////////////////////////////// 4066 4067function w3_menu(psa, cb) 4068{ 4069 var id = psa.replace(/\s+/g, ' ').split(' |')[0]; 4070 cb = cb || ''; 4071 //console.log('w3_menu id='+ id +' psa='+ psa); 4072 4073 // including mouseup here allows the button to continue to be pressed after the hold 4074 // and slid over the menu item to make a selection 4075 var slide_over = psa.includes('w3-slide-over')? 'onmouseup' : 'onclick'; 4076 var onclick = slide_over +'="w3int_menu_onclick(event, '+ sq(id) +', '+ sq(cb) +')"' + 4077 ' oncontextmenu="w3int_menu_onclick(event, '+ sq(id) +', '+ sq(cb) +')"'; 4078 var p = w3_psa(psa, 'w3-menu w3-menu-container w3-round-large', '', onclick); 4079 var s = '<div '+ p +'></div>'; 4080 //console.log('w3_menu s='+ s); 4081 w3_append_innerHTML('id-w3-misc-container', s); 4082} 4083 4084// menu items can be in argument list or passed as an array 4085function w3_menu_items(id, arr, max_vis, selected) 4086{ 4087 //console.log('w3_menu_items id='+ id); 4088 if (w3int.menu_debug) canvas_log('w3_menu_items cur='+ w3int.menu_cur_id); 4089 w3_menu_close('open'); // close any existing first 4090 w3int.menu_cur_id = id; 4091 4092 var s = ''; 4093 var i, idx = 0, prop, attr; 4094 var items; 4095 4096 if (isArray(arr)) { 4097 items = arr; 4098 } else { 4099 // isObject(arr) 4100 items = []; 4101 for (i=1; i < arguments.length; i++) 4102 items.push(arguments[i]); 4103 } 4104 4105 if (w3int.menu_debug) canvas_log('#='+ items.length); 4106 for (i=0; i < items.length; i++) { 4107 if (w3int.menu_debug) canvas_log('i='+ i +' '+ items[i]); 4108 if (items[i] == null) continue; 4109 prop = 'w3-menu '; 4110 attr = 'id='+ dq(idx); 4111 4112 if (items[i] == '<hr>') { 4113 prop += 'w3-menu-item-hr'; 4114 idx++; 4115 } else 4116 if (isString(items[i]) && items[i].charAt(0) == '!') { // first char == '!' hack to disable menu item 4117 prop += 'w3-menu-item-disabled'; 4118 items[i] = items[i].substr(1); 4119 idx++; 4120 } else { 4121 prop += 'w3-menu-item'; 4122 idx++; 4123 if (!kiwi_isMobile()) prop += ' w3-menu-item-hover'; 4124 if (isArg(selected) && selected[i]) 4125 prop += ' w3-menu-item-selected'; 4126 } 4127 var psa = w3_psa_mix('', prop, '', attr); 4128 s += w3_div(psa, items[i]); 4129 } 4130 4131 if (w3int.menu_debug) canvas_log('s#='+ s.length); 4132 //console.log(s); 4133 var el = w3_el(id); 4134 el.innerHTML = s; 4135 if (w3int.menu_debug) 4136 canvas_log('BUILD'); 4137 4138 // NB: If "overflow-y: auto" is done instead of "overflow-y:
4138scroll" (via w3-scroll-always-y) 4139 // then the scrollbar will intrude into the width of the menu elements and cause the longest 4140 // to lose right padding. 4141 max_vis = max_vis || items.length; 4142 if (items.length > max_vis) { 4143 el.style.height = px((max_vis * (4 + 19.5 + 4)) + 2*8); 4144 w3_add(el, 'w3-scroll-always-y'); 4145 } else { 4146 el.style.height = ''; 4147 w3_remove(el, 'w3-scroll-always-y'); 4148 } 4149 4150 // ":hover" highlighting doesn't work on mobile devices -- do it manually. 4151 // But NOT if the menu is long enough to be scrollable since there is no 4152 // gesture to distinguish mousing from scrolling, as with desktop. 4153 if (kiwi_isMobile()) { 4154 if (kiwi_util.mobile_laptop_test) console.log('w3_menu_items MOBILE '+ id); 4155 w3_iterate_children(id, function(el, i) { 4156 el.addEventListener('touchmove', w3int_menu_touch_move, w3.BUBBLING); 4157 if (kiwi_util.mobile_laptop_test) 4158 el.addEventListener('mousemove', w3int_menu_mouse_move, w3.BUBBLING); 4159 }); 4160 } 4161} 4162 4163// for testing 4164function w3int_menu_mouse_move(ev) 4165{ 4166 var x = Math.round(ev.pageX); 4167 var y = Math.round(ev.pageY); 4168 w3int_menu_move('MMM', x, y); 4169} 4170 4171function w3int_menu_touch_move(ev) 4172{ 4173 //event_dump(ev, 'w3int_menu_touch_move'); 4174 var x = Math.round(ev.touches[0].pageX); 4175 var y = Math.round(ev.touches[0].pageY); 4176 w3int_menu_move('MTM', x, y); 4177} 4178 4179function w3int_menu_move(which, x, y) 4180{ 4181 //console.log('w3int_menu_move '+ which); 4182 var el = document.elementFromPoint(x, y); 4183 if (!el) return; 4184 if (w3_isScrollingY(w3int.menu_cur_id)) return; 4185 var rtn = (!el || !w3_contains(el, 'w3-menu-item')); 4186 //console.log(which +' '+ x +':'+ y +':'+ el.id); 4187 //var el2 = w3_el(w3int.menu_cur_id); 4188 //console.log(el2.scrollTop +':'+ el2.scrollHeight +':'+ el2.clientHeight); 4189 4190 if (w3int.prev_menu_hover_el && (rtn || (el != w3int.prev_menu_hover_el))) { 4191 w3int.prev_menu_hover_el.style.background = ''; 4192 w3int.prev_menu_hover_el.style.color = ''; 4193 if (rtn) { 4194 //if (w3int.menu_debug) canvas_log('R'); 4195 return; 4196 } 4197 } 4198 4199 el.style.background = 'rgb(79, 157, 251)'; 4200 el.style.color = 'white'; 4201 w3int.prev_menu_hover_el = el; 4202} 4203 4204function w3_menu_popup(id, close_func, x, y) 4205{ 4206 //console.log('w3_menu_popup id='+ id +' x='+ x +' y='+ y); 4207 var x0 = x, y0 = y; 4208 var el = w3_el(id); 4209 x = x || window.innerWidth/2; 4210 y = y || window.innerHeight/2; 4211 4212 var slop = 16; 4213 el.w3_realigned = false; 4214 4215 if ((x + el.clientWidth) + slop > window.innerWidth) { 4216 x = window.innerWidth - el.clientWidth - slop; 4217 //if (w3int.menu_debug) canvas_log('X:'+ x0 +':'+ y0); 4218 el.w3_realigned_time = Date.now(); 4219 el.w3_realigned = true; 4220 } 4221 el.style.left = px(x); 4222 4223 var yt = y + el.clientHeight + slop; 4224 //if (w3int.menu_debug) canvas_log('Y:'+ x0 +':'+ y0 +'/'+ yt +'>'+ window.innerHeight +' '+ TF(yt > window.innerHeight)); 4225 if (yt > window.innerHeight) { 4226 y = window.innerHeight - el.clientHeight - slop; 4227 el.w3_realigned_time = Date.now(); 4228 el.w3_realigned = true; 4229 } 4230 el.style.top = px(y); 4231 //console.log('w3_menu_popup NEW id='+ id +' x='+ x +' y='+ y); 4232 4233 w3_visible(el, true); 4234 if (w3int.menu_debug) 4235 canvas_log('VIS'); 4236 w3int.menu_active = true; 4237 el.w3_menu_x = x; 4238 4239 // close menu if escape key while menu is displayed 4240 w3int.menu_close_func = close_func; 4241 w3int.menu_first = true; 4242 window.addEventListener("keyup", w3int_menu_event, w3.BUBBLING); 4243 window.addEventListener("mousedown", w3int_menu_event, w3.BUBBLING); 4244 window.addEventListener("touchstart", w3int_menu_event, w3.BUBBLING); 4245 window.addEventListener("click", w3int_menu_event, w3.BUBBLING); 4246} 4247 4248function w3_menu_active() 4249{ 4250 return w3int.menu_active; 4251} 4252 4253function w3int_menu_onclick(ev, id, cb, cb_param) 4254{ 4255 if (w3int.menu_debug) 4256 canvas_log('menu_onclick '+ id +' from='+ (cb_param || ev.type) +' cb='+ cb); 4257 //console.info('w3int_menu_onclick id='+ id +' cb='+ cb); 4258 //if (ev != null) event_dump(ev, "MENU", false); 4259 var el = w3_el(id); 4260 4261 // ignore false click from menu re-alignment (only if click is recent enough) 4262 //console.log('w3int_menu_onclick realigned='+ el.w3_realigned); 4263 if (el.w3_realigned) { 4264 el.w3_realigned = false;
4265 var when = Date.now() - el.w3_realigned_time; 4266 if (when < 50) { 4267 if (w3int.menu_debug) canvas_log('XRe'+ when); 4268 //console.log('w3int_menu_onclick: cancelEvent()'); 4269 return cancelEvent(ev); 4270 } 4271 if (w3int.menu_debug) canvas_log('XOK'+ when); 4272 } 4273 4274 //if (cb == null) kiwi_trace('w3int_menu_onclick'); 4275 if (ev != null && cb != null) { 4276 var _id = ev.target.id; 4277 var idx = +_id; 4278 if (_id == '' || isNaN(idx)) _id = ev.target.parentNode.id; 4279 idx = +_id; 4280 if (_id == '' || isNaN(idx)) idx = -1; 4281 //console.log('w3int_menu_onclick CALL idx='+ idx); 4282 if (w3int.menu_debug) 4283 canvas_log('CALL: '+ idx); 4284 if (idx == -1) return; // clicked/touched top/bottom border -- don't dismiss menu 4285 w3_call(cb, idx, el.w3_menu_x, cb_param, ev); 4286 } 4287 4288 w3_visible(el, false); 4289 //console.log('w3int_menu_onclick NOT_VIS'); 4290 if (w3int.menu_debug) 4291 canvas_log('NOT_VIS'); 4292 w3int.menu_active = false; 4293 window.removeEventListener("keyup", w3int_menu_event, w3.BUBBLING); 4294 window.removeEventListener("mousedown", w3int_menu_event, w3.BUBBLING); 4295 window.removeEventListener("touchstart", w3int_menu_event, w3.BUBBLING); 4296 window.removeEventListener("click", w3int_menu_event, w3.BUBBLING); 4297 4298 // allow right-button to select menu items 4299 if (ev != null) { 4300 if (w3int.menu_debug) canvas_log('Enull'); 4301 return cancelEvent(ev); 4302 } 4303} 4304 4305// close menu if caller's close_func() returns true 4306function w3int_menu_event(evt) 4307{ 4308 if (w3int.menu_debug) canvas_log('ME '+ evt.type); 4309 var el = w3_el(w3int.menu_cur_id); 4310 //console.info('w3int_menu_event'); 4311 //console.log(el); 4312 //event_dump(evt, 'MENU-EVT', true); 4313 4314 if (el && w3int.menu_close_func) { 4315 //console.log(w3int.menu_close_func); 4316 var close = w3int.menu_close_func(evt, w3int.menu_first); 4317 //console.log('w3int_menu_event CLOSE close='+ close +' first='+ w3int.menu_first); 4318 w3int.menu_first = false; 4319 if (w3int.menu_debug) canvas_log('CLOSE='+ close); 4320 if (close) w3_menu_close(evt.type); 4321 } 4322 //else canvas_log('no w3_menu_close()'); 4323} 4324 4325function w3_menu_close(from) 4326{ 4327 //console.info('w3_menu_close'); 4328 if (w3int.menu_debug) canvas_log('w3_menu_close '+ from +' id='+ w3int.menu_cur_id); 4329 if (!w3int.menu_cur_id) return; 4330 var el = w3_el(w3int.menu_cur_id); 4331 if (!el) return; 4332 el.w3_realigned = false; 4333 w3int_menu_onclick(null, w3int.menu_cur_id, null, 'menu_close'); 4334 w3int.menu_cur_id = null; 4335} 4336 4337 4338//////////////////////////////// 4339// standard callbacks -- only set var 4340//////////////////////////////// 4341 4342function w3_num_cb(path, val) 4343{ 4344 var v = parseFloat(val); 4345 if (val == '') v = 0; 4346 if (isNaN(v)) { 4347 //console.log('w3_num_cb path='+ path); 4348 w3_set_value(path, 0); 4349 v = 0; 4350 } 4351 //console.log('w3_num_cb: path='+ path +' val='+ val +' v='+ v); 4352 setVarFromString(path, v); 4353} 4354 4355function w3_bool_cb(path, val) 4356{ 4357 v = +val; 4358 //console.log('w3_bool_cb: path='+ path +' val='+ val +' v='+ v); 4359 setVarFromString(path, v); 4360} 4361 4362function w3_string_cb(path, val) 4363{ 4364 //console.log('w3_string_cb: path='+ path +' val='+ val); 4365 setVarFromString(path, val.toString()); 4366} 4367 4368function w3_select_cb(path, val) 4369{ 4370 val = +val; 4371 //console.log('w3_select_cb: path='+ path +' val='+ val); 4372 setVarFromString(path, val); 4373} 4374 4375 4376 4377//////////////////////////////// 4378// standard callbacks -- set cfg and var 4379//////////////////////////////// 4380 4381function w3_num_set_cfg_cb(path, val, first) 4382{ 4383 var v = parseFloat(val); 4384 if (isNaN(v)) v = 0; 4385 4386 // if first time don't save, otherwise always save 4387 var save = isArg(first)? (first? false : true) : true; 4388 ext_set_cfg_param(path, v, save); 4389} 4390 4391function w3_int_set_cfg_cb(path, val, first) 4392{ 4393 var v = parseInt(val); 4394 if (isNaN(v)) v = 0; 4395 w3_set_value(path, v); // remove any fractional or non-number portion from field 4396 4397 // if first time don't save, otherwise always save 4398 var save = isArg(first)? (first? false : true) : true; 4399 ext_set_cfg_param(path, v, save); 4400} 4401 4402// limit precision using callback spec: 'w3_float_set_cfg_cb|prec' 4403function w3_float_set_cfg_cb(path, val, first, cb_a) 4404{ 4405 var prec = -1; // default to no precision limiting applied 4406 //console.log('admin_float_cb '+ path +'='+ val +' cb_a.len='+ cb_a.length); 4407 if (isArray(cb_a) && cb_a.length >= 2) { 4408 prec = +(cb_a[1]); 4409 if (isNaN(prec)) prec = -1; 4410 //console.log('admin_float_cb prec='+ prec); 4411 } else 4412 if (isNumber(cb_a)) { 4413 prec = cb_a; 4414 if (isNaN(prec)) prec = -1; 4415 } 4416 val = parseFloat(val); 4417 if (isNaN(val)) { 4418 // put old value back 4419 val = ext_get_cfg_param(path); 4420 } else { 4421 if (prec != -1) { 4422 var s = val.toFixed(prec); // NB: .toFixed() does rounding 4423 //console.log('admin_float_cb val('+ prec +')='+ s); 4424 val = +s; 4425 } 4426 // if first time don't save, otherwise always save 4427 var save = isArg(first)? (first? false : true) : true; 4428 ext_set_cfg_param(path, val, save); 4429 } 4430 w3_set_value(path, val); // remove any non-numeric part from field 4431} 4432 4433function w3_bool_set_cfg_cb(path, val, first) 4434{ 4435 var v; 4436 if (val == true || val == 1) v = true; else 4437 if (val == false || val == 0) v = false; else 4438 v = false;
4439 4440 // if first time don't save, otherwise always save 4441 var save = isArg(first)? (first? false : true) : true; 4442 ext_set_cfg_param(path, v, save); 4443} 4444 4445function w3_string_old_set_cfg_cb(path, val, first) 4446{ 4447 console.log('w3_string_old_set_cfg_cb: path='+ path +' '+ typeof(val) +' <'+ val +'> first='+ first); 4448 4449 // if first time don't save, otherwise always save 4450 var save = isArg(first)? (first? false : true) : true; 4451 ext_set_cfg_param(path, encodeURIComponent(val.toString()), save); 4452} 4453 4454function w3_string_set_cfg_cb(path, val, first) 4455{ 4456 //console.log('w3_string_set_cfg_cb: path='+ path +' '+ typeof(val) +' <'+ val +'> first='+ first); 4457 w3_json_set_cfg_cb(path, val, first); 4458} 4459 4460function w3_json_set_cfg_cb(path, val, first) 4461{ 4462 //console.log('w3_json_set_cfg_cb: path='+ path +' '+ typeof(val) +' <'+ val +'> first='+ first); 4463 4464 // verify that value is valid JSON 4465 var v = val; 4466 if (isString(v)) v = JSON.stringify(v); 4467 try { 4468 JSON.parse(v); 4469 } catch(ex) { 4470 console.log('w3_json_set_cfg_cb: path='+ path +' '+ typeof(val) +' <'+ val +'> first='+ first); 4471 console.log('w3_json_set_cfg_cb: DANGER value did not parse as JSON!'); 4472 return; 4473 } 4474 4475 // if first time don't save, otherwise always save 4476 var save = isArg(first)? (first? false : true) : true; 4477 ext_set_cfg_param(path, val, save); 4478} 4479 4480// like w3_string_set_cfg_cb(), but enforces specification of leading 'http://' or 'https://' 4481function w3_url_set_cfg_cb(path, val, first) 4482{ 4483 //console.log('w3_url_set_cfg_cb: path='+ path +' '+ typeof(val) +' <'+ val +'> first='+ first); 4484 if (val != '' && !val.startsWith('http://') && !val.startsWith('https://')) val = 'http://'+ val; 4485 w3_string_set_cfg_cb(path, val, first); 4486 w3_set_value(path, val); 4487} 4488 4489 4490// path is structured to be: [el_name][sep][idx] 4491// e.g. [id-dx.o.ty][_][123] 4492function w3_remove_trailing_index(path, sep) 4493{ 4494 sep = sep || '-'; // optional separator, e.g. if negative indicies are used 4495 var re = new RegExp('(.*)'+ sep +'([-]?[0-9]+)'); 4496 var el = re.exec(path); // remove trailing -nnn 4497 //console.log('w3_remove_trailing_index path='+ path +' el='+ el); 4498 var idx; 4499 if (!el) { 4500 idx = null; 4501 el = path; 4502 } else { 4503 idx = el[2]; 4504 el = el[1]; 4505 } 4506 return { el:el, idx:idx }; 4507} 4508 4509var w3_MENU_NEXT = 1; 4510var w3_MENU_PREV = -1; 4511 4512// cb_param = { dir:w3_MENU_{NEXT,PREV}, id:'...', func:'...', (optional, to skip non-numeric menu items) isNumeric:true } 4513function w3_select_next_prev_cb(path, cb_param, first) 4514{ 4515 var val; 4516 if (dbgUs) console.log('w3_select_next_prev_cb cb_param='+ decodeURIComponent(cb_param)); 4517 var cbp = w3_cb_param_decode(cb_param); 4518 if (!isObject(cbp)) { 4519 console.log('w3_select_next_prev_cb cb_param is not an object'); 4520 return; 4521 } 4522 if (dbgUs) console.log('w3_select_next_prev_cb dir='+ cbp.dir +' id='+ cbp.id); 4523 4524 // if any menu has a selection value then select next/prev (if any) 4525 var prev = 0, capture_the_next = 0, captured_next = 0, captured_prev = 0; 4526 var menu_s, el; 4527 4528 for (var i = 0; (el = w3_el(menu_s = cbp.id + i)) != null; i++) { 4529 val = el.value; 4530 if (dbgUs) console.log('menu_s='+ menu_s +' value='+ val); 4531 if (val == -1) continue; 4532 4533 w3_select_enum(menu_s, function(option) { 4534 var content_check = (isUndefined(cbp.isNumeric) || (cbp.isNumeric == true && isNumber(parseInt(option.innerText)))); 4535 if (option.disabled || !content_check) return; 4536 if (dbgUs) console.log('consider val='+ option.value +' \"'+ option.innerText +'\" content_check='+ content_check); 4537 if (capture_the_next) { 4538 captured_next = option.value; 4539 capture_the_next = 0; 4540 } 4541 if (option.value === val) { 4542 captured_prev = prev; 4543 capture_the_next = 1; 4544 } 4545 prev = option.value; 4546 }); 4547 break; 4548 } 4549 4550 if (dbgUs) console.log('i='+ i +' captured_prev='+ captured_prev +' captured_next='+ captured_next); 4551 val = 0; 4552 if (cbp.dir == w3_MENU_NEXT && captured_next) val = captured_next; 4553 if (cbp.dir == w3_MENU_PREV && captured_prev) val = captured_prev; 4554 if (dbgUs) console.log('w3_select_next_prev_cb val='+ val); 4555 //if (val) 4556 w3_call(cbp.func, menu_s, val, first); 4557} 4558 4559 4560//////////////////////////////// 4561// tables 4562//////////////////////////////// 4563 4564// caller can choose more specific table type, e.g. w3-table-fixed 4565// FIXME: deprecated, only still used in admin GPS 4566function w3_table(psa) 4567{ 4568 var p = w3_psa(psa, 'w3-table w3-table-default'); 4569 var s = '<table '+ p +'>'; 4570 for (var i=1; i < arguments.length; i++) { 4571 s += arguments[i]; 4572 } 4573 s += '</table>'; 4574 //console.log(s); 4575 return s; 4576} 4577 4578function w3_table_head(psa) 4579{ 4580 var p = w3_psa(psa); 4581 var s = '<thead '+ p +'>'; 4582 for (var i=1; i < arguments.length; i++) { 4583 if (arguments[i] == null) continue; 4584 s += arguments[i]; 4585 } 4586 s += '</thead>'; 4587 //console.log(s); 4588 return s; 4589} 4590 4591function w3_table_body(psa) 4592{ 4593 var p = w3_psa(psa); 4594 var s = '<tbody '+ p +'>'; 4595 for (var i=1; i < arguments.length; i++) { 4596 if (arguments[i] == null) continue; 4597 s += arguments[i]; 4598 } 4599 s += '</tbody>'; 4600 //console.log(s); 4601 return s; 4602} 4603 4604function w3_table_heads(psa) 4605{ 4606 var p = w3_psa(psa, 'w3-table-head'); 4607 var s = ''; 4608 for (var i=1; i < arguments.length; i++) { 4609 if (arguments[i] == null) continue; 4610 s += '<th '+ p +'>'; 4611 s += arguments[i]; 4612 s += '</th>'; 4613 } 4614 //console.log(s); 4615 return s; 4616} 4617 4618function w3_table_row(psa) 4619{ 4620 var p = w3_psa(psa, 'w3-table-row'); 4621 var s = '<tr '+ p +'>'; 4622 for (var i=1; i < arguments.length; i++) { 4623 if (arguments[i] == null) continue; 4624 s += arguments[i]; 4625 } 4626 s += '</tr>'; 4627 //console.log(s); 4628 return s; 4629} 4630 4631function w3_table_cells(psa) 4632{ 4633 var p = w3_psa(psa, 'w3-table-cell'); 4634 var s = ''; 4635 for (var i=1; i < arguments.length; i++) { 4636 if (arguments[i] == null) continue; 4637 s += '<td '+ p +'>'; 4638 s += arguments[i]; 4639 s += '</td>'; 4640 } 4641 //console.log(s); 4642 return s; 4643} 4644 4645 4646//////////////////////////////// 4647// containers 4648//////////////////////////////// 4649 4650// If any psa prop or style is of the form N:{prop,style} then only use prop or style if N == current arg number 4651// e.g. 4652// w3_inline('foo/bar 1:baz 2:|zzz:123', w3_div('zero'), w3_div('one'), w3_div('two')) => 4653// <div class="foo"> 4654// <div class="bar zero"> ... </div> 4655// <div class="bar one baz"> ... </div> 4656// <div class="bar two" style="zzz:123"> ... </div> 4657// </div> 4658// This is a generalization of the w3-btn-right feature of w3int_button() 4659 4660function w3_inline(psa, attr) 4661{ 4662 var narg = arguments.length;
4663 var dump = psa.includes('w3-dump'); 4664 var div_or_span = psa.includes('w3-span')? 'span' : 'div'; 4665 var enclosing = !psa.includes('w3-no-encl'); 4666 if (dump) console.log(arguments); 4667 4668 if (psa == '' && attr == '' && narg > 2) { 4669 console.log('### w3_inline OLD API DEPRECATED'); 4670 var args = Array.prototype.splice.call(arguments, 0); 4671 args.splice(0, 1); 4672 return w3_inline.apply(null, args); 4673 } else { 4674 var psa3 = w3_psa3(psa); 4675 var psa_outer = w3_psa(psa3.middle, 'w3-inline-flex'); 4676 var psa_inner = w3_psa(psa3.right); 4677 if (dump) console.log('w3_inline psa_outer='+ psa_outer +' psa_inner='+ psa_inner +' enclosing='+ enclosing); 4678 var s = '<'+ div_or_span +' w3d-inlo '+ psa_outer +'>'; 4679 4680 // for each of the inline items 4681 for (var i = 1; i < narg; i++) { 4682 var a = arguments[i]; 4683 if (!a) continue; 4684 if (dump) console.log('w3_inline i='+ i +' a='+ a); 4685 4686 // merge a pseudo psa specifier into the next argument 4687 // e.g. 'w3-*', w3_*('w3-psa') => w3_*('w3-* w3-psa') 4688 var psa_merge = false; 4689 if (a.startsWith('w3-') || a.startsWith('id-')) { 4690 psa = w3_psa(psa3.right, a); 4691 i++; 4692 a = arguments[i]; 4693 psa_merge = true; 4694 } else { 4695 if (psa_inner != '') { 4696 // apply N:{prop,style} to corresponding inline element if present 4697 var psa3r = ''; 4698 var a1 = psa3.right.split(' '); 4699 a1.forEach( 4700 function(p1) { 4701 if (dump) console.log('N:{prop,style} i='+ (i-1) +' p1='); 4702 var a2 = p1.split(':'); 4703 if (dump) console.log(a2); 4704 if (a2.length >= 2) { 4705 var n = +a2[0]; 4706 var match = (n == (i-1)); 4707 if (dump) console.log('i='+ i +' n='+ n +' '+ match); 4708 var isBar = (a2[1][1] == '|'); 4709 a2.shift(); 4710 rem = a2.join(':'); 4711 var xprop = (match && !isBar)? rem : ''; 4712 var xstyle = (match && isBar)? rem.slice(1) : ''; 4713 psa3r = w3_sb(psa3r, xprop, xstyle); 4714 } else { 4715 psa3r = w3_sb(psa3r, p1); 4716 } 4717 } 4718 ); 4719 if (dump) console.log('psa3r='+ psa3r); 4720 psa = w3_psa(psa3r); 4721 if (dump) console.log('psa='+ psa); 4722 } else { 4723 psa = ''; 4724 } 4725 } 4726 4727 // If the psa is empty, and the arg is a div, don't wrap it with our usual enclosing div. 4728 // This solves the "w3_inline() + w3-hide" problem where our extra div 4729 // added by w3_inline isn't the one with the w3-hide, and causes unwanted spacing when 4730 // using w3_inline('w3-halign-space-between/'). 4731 if (psa3.right == '' && !psa_merge && a.startsWith('<div ')) { 4732 if (dump) console.log('FINAL-1 '+ a); 4733 s += a; 4734 } else { 4735 if (enclosing) { 4736 a = '<'+ div_or_span +' w3d-inli-'+ w3_sb(i-1, psa) +'>'+ a + '</'+ div_or_span +'>'; 4737 if (dump) console.log('FINAL-2 '+ a); 4738 s += a; 4739 } else { 4740 if (dump) console.log('FINAL-3 '+ a); 4741 s += a; 4742 } 4743 } 4744 } 4745 s += '</'+ div_or_span +'>'; 4746 if (dump) console.log(s); 4747 return s; 4748 } 4749} 4750 4751// call w3_inline when parameters need to be accumulated in an array 4752function w3_inline_array(psa, ar) 4753{ 4754 ar.unshift(psa); // add psa to head of array 4755 //console.log(ar); 4756 return w3_inline.apply(null, ar); 4757} 4758 4759function w3_inline_noencl() 4760{ 4761 var a = Array.from(arguments); 4762 psa = w3_psa3(a[0]); 4763 a[0] = w3_sb(psa.middle, 'w3-no-encl/') + psa.right; 4764 return w3_inline.apply(null, a); 4765} 4766 4767// see: stackoverflow.com/questions/29885284/how-to-set-a-fixed-width-column-with-css-flexbox 4768// 4769// If any psa prop is of the form N:prop then only use prop if N == current arg number 4770// e.g. 4771// w3_inline_percent('foo/bar 1:baz', w3_div('zero'), w3_div('one'), w3_div('two')) => 4772// <div class="foo"> 4773// <div class="bar zero"> ... </div> 4774// <div class="bar baz one"> ... </div> 4775// <div class="bar two"> ... </div> 4776// </div> 4777// This is a generalization of the w3-btn-right feature of w3int_button() 4778 4779function w3_inline_percent(psa) 4780{ 4781 var narg = arguments.length;
4782 var dump = psa.includes('w3-dump'); 4783 var psa3 = w3_psa3(psa); 4784 var psa_outer = w3_psa(psa3.middle, 'w3-inline-flex'); 4785 var psa_inner = w3_psa(psa3.right); 4786 if (dump) console.log('w3_inline_percent psa_outer='+ psa_outer +' psa_inner='+ psa_inner); 4787 var total = 0; 4788 var prop = psa.includes('w3-debug')? 'w3_debug ' : ''; 4789 var last_halign_end = psa.includes('w3-last-halign-end'); 4790 var s = '<div w3d-inlpo '+ psa_outer +'>'; 4791 for (var i = 1; i < narg; i += 2) { 4792 var im1d2 = (i-1)/2; 4793 var style; 4794 if (i+1 < narg) { 4795 style = 'flex-basis:'+ arguments[i+1] +'%'; 4796 total += arguments[i+1]; 4797 } else { 4798 style = 'flex-basis:'+ (100 - total) +'%'; 4799 } 4800 if (i == narg-1 && last_halign_end) prop += 'w3-halign-end'; 4801 if (psa_inner != '') { 4802 var psa3r = ''; 4803 var a1 = psa3.right.split(' '); 4804 a1.forEach( 4805 function(p1) { 4806 if (dump) console.log('N:prop i='+ (im1d2-0) +' p1='); 4807 var a2 = p1.split(':'); 4808 if (dump) console.log(a2); 4809 if (a2.length == 2) { 4810 var n = +a2[0]; 4811 if (dump) console.log('i='+ i +' n='+ n +' '+ (n == (im1d2-0))); 4812 psa3r = w3_sb(psa3r, (n == (im1d2-0))? a2[1] : ''); 4813 } else { 4814 psa3r = w3_sb(psa3r, p1); 4815 } 4816 } 4817 ); 4818 if (dump) console.log('w3_inline_percent prop='+ prop +' style='+ style); 4819 psa = w3_psa(psa3r, prop, style); 4820 } else { 4821 psa = w3_psa(psa3.right, prop, style); 4822 } 4823 s += '<div w3d-inlpi-'+ w3_sb(im1d2, psa) +'>'+ (arguments[i] || '') + '</div>'; 4824 } 4825 s += '</div>'; 4826 if (dump) console.log(s); 4827 return s; 4828} 4829 4830function w3_inline_percent_set(id, p) 4831{ 4832 var el = w3_el(id); 4833 el.style.flexBasis = pct(p); 4834} 4835 4836/* 4837// old version using div width:% 4838function w3_inline_percent_old(psa) 4839{ 4840 var psa3 = w3_psa3(psa); 4841 var psa_outer = w3_psa(psa3.middle, 'w3-inline-flex'); 4842 var narg = arguments.length; 4843 var total = 0; 4844 var s = '<div w3d-inlpo '+ psa_outer +'>'; 4845 for (var i = 1; i < narg; i += 2) { 4846 var style; 4847 if (i+1 < narg) { 4848 style = 'width:'+ arguments[i+1] +'%'; 4849 total += arguments[i+1]; 4850 } else { 4851 style = 'width:'+ (100 - total) +'%'; 4852 } 4853 s += '<div w3d-inlpi-'+ ((i-1)/2) +' '+ w3_psa(psa3.right, '', style) +'>'+ arguments[i] + '</div>'; 4854 } 4855 s += '</div>'; 4856 //console.log(s); 4857 return s; 4858} 4859*/ 4860 4861function w3_div(psa) 4862{ 4863 var p = w3_psa(psa); 4864 var s = '<div w3d-div '+ p +'>'; 4865 var narg = arguments.length; 4866 for (var i=1; i < narg; i++) { 4867 s += arguments[i]; 4868 } 4869 s += '</div>'; 4870 //console.log(s); 4871 return s; 4872} 4873 4874function w3_span(psa) 4875{ 4876 var div_or_span = psa.includes('w3-span')? 'span' : 'div'; 4877 var p = w3_psa(psa, 'w3-show-span'); 4878 var s = '<'+ div_or_span +' w3d-span '+ p +'>'; 4879 var narg = arguments.length; 4880 for (var i=1; i < narg; i++) { 4881 s += arguments[i]; 4882 } 4883 s += '</'+ div_or_span +'>'; 4884 //console.log(s); 4885 return s; 4886} 4887 4888function w3_divs(psa, attr) 4889{ 4890 var narg = arguments.length; 4891 var s, args; 4892 4893 if (psa == '' && attr == '') { 4894 console.log('### w3_divs OLD API 1 DEPRECATED'); 4895 //console.log('prop=<'+ psa +'> attr=<'+ attr +'>'); 4896 //kiwi_trace(); 4897 s = w3_div.apply(null, Array.prototype.splice.call(arguments, 1)); 4898 //console.log(s); 4899 return s; 4900 } else 4901 if (psa != '' && attr == '' && narg > 2) { 4902 console.log('### w3_divs OLD API 2 DEPRECATED'); 4903 //console.log('prop=<'+ psa +'> attr=<'+ attr +'>'); 4904 //kiwi_trace(); 4905 args = Array.prototype.splice.call(arguments, 0); 4906 args.splice(1, 1); 4907 s = w3_div.apply(null, args); 4908 //console.log(s); 4909 return s; 4910 } else 4911 if (psa == '' && attr && attr.startsWith('w3-') && narg > 2) { 4912 console.log('### w3_divs OLD API 3 DEPRECATED'); 4913 //console.log('prop=<'+ psa +'> attr=<'+ attr +'>'); 4914 //kiwi_trace(); 4915 args = Array.prototype.splice.call(arguments, 0); 4916 args.splice(0, 1);
4917 s = w3_divs.apply(null, args); 4918 //console.log(s); 4919 return s; 4920 } else 4921 if (psa != '' && attr && attr.startsWith('w3-') && narg > 2) { 4922 console.log('### w3_divs OLD API 4 DEPRECATED'); 4923 //console.log('prop=<'+ psa +'> attr=<'+ attr +'>'); 4924 //kiwi_trace(); 4925 args = Array.prototype.splice.call(arguments, 2); 4926 args.splice(0, 0, arguments[0] +'/'+ arguments[1]); 4927 s = w3_divs.apply(null, args); 4928 //console.log(s); 4929 return s; 4930 } else { 4931 var psa3 = w3_psa3(psa); 4932 var psa_outer = w3_psa(psa3.middle); 4933 var psa_inner = w3_psa(psa3.right); 4934 s = '<div w3d-divso '+ psa_outer +'>'; 4935 for (var i=1; i < narg; i++) { 4936 s += '<div w3d-divsi-'+ (i-1) +' '+ psa_inner +'>'+ arguments[i] + '</div>'; 4937 } 4938 s += '</div>'; 4939 //console.log(s); 4940 return s; 4941 } 4942} 4943 4944function w3_col_percent(psa) 4945{ 4946 var psa3 = w3_psa3(psa); 4947 var psa_outer = w3_psa(psa3.middle, 'w3-row'); 4948 var narg = arguments.length; 4949 var s = '<div w3d-colpo '+ psa_outer +'>'; 4950 for (var i = 1; i < narg; i += 2) { 4951 var prop, style; 4952 if (i+1 < narg) { 4953 prop = 'w3-col'; 4954 style = 'width:'+ arguments[i+1] +'%'; 4955 } else { 4956 prop = 'w3-rest'; 4957 style = ''; 4958 } 4959 s += '<div w3d-colpi-'+ ((i-1)/2) +' '+ w3_psa(psa3.right, prop, style) +'>'+ arguments[i] + '</div>'; 4960 } 4961 s += '</div>'; 4962 //console.log(s); 4963 return s; 4964} 4965 4966// the w3-text makes it inline-block, so no surrounding w3_inline() needed 4967function w3_text(psa, text) 4968{ 4969 var style = (psa.includes('w3-padding') || psa.includes('w3-nopad'))? '' : 'padding:0 4px 0 0'; 4970 if (!psa.includes('w3-nobk')) style += '; background-color:inherit'; 4971 var s = w3_div(w3_psa_mix(psa, 'w3-text', style), text? text:' '); 4972 //console.log(s); 4973 return s; 4974} 4975 4976function w3_code(psa) 4977{ 4978 var psa3 = w3_psa3(psa); 4979 var narg = arguments.length; 4980 var s = '<pre '+ w3_psa(psa3.left) +'><code '+ w3_psa(psa3.middle) +'>'; 4981 for (var i=1; i < narg; i++) { 4982 s += '<code '+ w3_psa(psa3.right) +'>'+ arguments[i] + '</code>'; 4983 } 4984 s += '</code></pre>'; 4985 //console.log(s); 4986 return s; 4987} 4988 4989function w3_header(psa, size) 4990{ 4991 var psa3 = w3_psa3(psa); 4992 //console.log(psa3); 4993 var narg = arguments.length; 4994 var s = '<header '+ w3_psa(psa3.middle) +'><h'+ size +' '+ w3_psa(psa3.right) +'>'; 4995 for (var i=2; i < narg; i++) { 4996 s += arguments[i]; 4997 } 4998 s += '</h'+ size +'></header>'; 4999 //console.log(s); 5000 return s; 5001} 5002 5003/* 5004function w3_half(prop_row, prop_col, left, right, prop_left, prop_right) 5005{ 5006 if (prop_left == undefined) prop_left = ''; 5007 if (prop_right == undefined) prop_right = ''; 5008 5009 var s = 5010 '<div class="w3-row '+ prop_row +'">' + 5011 '<div class="w3-col w3-half '+ prop_col + prop_left +'">' + 5012 left + 5013 '</div>' + 5014 '<div class="w3-col w3-half '+ prop_col + prop_right +'">' + 5015 (right? right:'') + 5016 '</div>' + 5017 '</div>'; 5018 //console.log(s); 5019 return s; 5020} 5021*/ 5022 5023function w3_half(prop_row, prop_col, left, right) 5024{ 5025 return w3_inline_percent(w3_sbc('/', prop_row, prop_col), left, 50, right); 5026} 5027 5028/* 5029function w3_third(prop_row, prop_col, left, middle, right) 5030{ 5031 var s = 5032 '<div class="w3-row '+ prop_row +'">' + 5033 '<div class="w3-col w3-third '+ prop_col +'">' + 5034 left + 5035 '</div>' + 5036 '<div class="w3-col w3-third '+ prop_col +'">' + 5037 (middle? middle:'') + 5038 '</div>' + 5039 '<div class="w3-col w3-third '+ prop_col +'">' + 5040 (right? right:'') + 5041 '</div>' + 5042 '</div>'; 5043 //console.log(s); 5044 return s; 5045} 5046*/ 5047 5048function w3_third(prop_row, prop_col, left, middle, right) 5049{ 5050 return w3_inline_percent(w3_sbc('/', prop_row, prop_col), left, 33, middle, 33, right); 5051} 5052 5053/* 5054function w3_quarter(prop_row, prop_col, left, middleL, middleR, right) 5055{ 5056 var s = 5057 '<div class="w3-row '+ prop_row +'">' + 5058 '<div class="w3-col w3-quarter '+ prop_col +'">' + 5059 left + 5060 '</div>' + 5061 '<div class="w3-col w3-quarter '+ prop_col +'">' + 5062 (middleL? middleL:'') + 5063 '</div>' + 5064 '<div class="w3-col w3-quarter '+ prop_col +'">' + 5065 (middleR? middleR:'') + 5066 '</div>' + 5067 '<div class="w3-col w3-quarter '+ prop_col +'">' + 5068 (right? right:'') + 5069 '</div>' + 5070 '</div>'; 5071 //console.log(s); 5072 return s; 5073} 5074*/ 5075 5076function w3_quarter(prop_row, prop_col, left, middleL, middleR, right) 5077{ 5078 return w3_inline_percent(w3_sbc('/', prop_row, prop_col), left, 25, middleL, 25, middleR, 25, right); 5079} 5080 5081function w3_canvas(psa, w, h, opt) 5082{ 5083 var s = ''; 5084 var pad = w3_opt(opt, 'pad', null); 5085 var pad_top = w3_opt(opt, 'pad_top', 0); 5086 var pad_bot = w3_opt(opt, 'pad_bottom', 0); 5087 if (!isNull(pad)) pad_top = pad_bot = pad; 5088 pad = pad_top + pad_bot; 5089 if (pad) { 5090 //w -= pad*2; h -= pad*2; 5091 s += sprintf(' padding-top:%dpx; padding-bottom:%dpx;', pad_top, pad_bot); 5092 } 5093 5094 var left = w3_opt(opt, 'left', ''); 5095 if (left != '') s += sprintf(' left:%dpx;', left); 5096 var right = w3_opt(opt, 'right', ''); 5097 if (right != '') s += sprintf(' right:%dpx;', right); 5098 var top = w3_opt(opt, 'top', ''); 5099 if (top != '') s += sprintf(' top:%dpx;', top); 5100 5101 s = '<canvas '+ w3_psa(psa, null, 'position:absolute;'+ s, sprintf('width="%d" height="%d"', w, h)) +'></canvas>'; 5102 //console.log(s); 5103 return s; 5104}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.