otsdaq_utilities  v2_02_00
MultiSelectBox.js
1 //
5 // To make a Multi-Select Box
6 // create a div element and call in JavaScript...
7 //
8 // MultiSelectBox.createSelectBox(el,name,title,vals)
9 //
10 // This function is called by user to actually create the multi select box
11 // These parameters are optional and can be omitted or set to 0:
12 // keys, types, handler, noMultiSelect,mouseOverHandler,iconURLs,mouseDownHandler,
13 // mouseUpHandler,
14 // requireCtrlMultiClick,titles
15 // Note: handler is the string name of the function (put in quotes).
16 // Note: requireCtrlMultiClick enables CONTROL or SHIFT key selections
17 // - sometimes CONTROL forces right click in browser, so SHIFT is needed
18 //
19 // Can use MultiSelectBox.initMySelectBoxes after manually setting the mySelects_ array
20 //
21 //
22 //
23 // Example selection handler:
24 //
25 // function exampleSelectionHandler(el)
26 // {
27 // var splits = el.id.split('_');
28 // var i = splits[splits.length-1] | 0;
29 // MultiSelectBox.dbg("Chosen element index:",i,
30 // " key:",el.getAttribute("key-value"),
31 // " type:",el.getAttribute("type-value"));
32 // for(var s in MultiSelectBox.mySelects_[el.parentElement.id])
33 // MultiSelectBox.dbg("selected: ",MultiSelectBox.mySelects_[el.parentElement.id][s]);
34 //
35 // }
36 //
37 // Example usage: /WebPath/html/MultiSelectBoxTest.html
38 //
41 
42 var selected = [];
43 var MultiSelectBox = MultiSelectBox || {}; //define MultiSelectBox namespace
44 
45 if(window.console && console && console.log)
46  MultiSelectBox.dbg = console.log.bind(window.console);
47 //var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
48 
49 function $(id) {return document.getElementById(id);}
50 
52 //global variables
53 
54 MultiSelectBox.mySelects_ = {};
55 MultiSelectBox.omnis_ = {};
56 MultiSelectBox.isSingleSelect_ = {};
57 MultiSelectBox.lastOptSelect_ = {}; //maintain last opt clicked
58 
59 MultiSelectBox.selInitBoxHeight_ = {}; //init with first showing of search box in showSearch()
60 MultiSelectBox.SEL_INIT_PADDING = 5;
61 
62 MultiSelectBox.requireCtrlMultiClick_ = {};
63 
65 //function definitions
66 
67 MultiSelectBox.hasClass = function(ele,cls)
68 {
69  return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
70 }
71 
72 MultiSelectBox.addClass = function(ele,cls)
73 {
74  if (!MultiSelectBox.hasClass(ele,cls)) ele.className += " "+cls;
75 }
76 
77 MultiSelectBox.removeClass = function(ele,cls)
78 {
79  if (MultiSelectBox.hasClass(ele,cls))
80  {
81  var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
82  ele.className=ele.className.replace(reg,'');
83  }
84 }
85 
86 MultiSelectBox.toggleClass = function(ele,cls)
87 {
88  //returns true if the element had the class
89  if (MultiSelectBox.hasClass(ele,cls))
90  {
91  var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
92  ele.className=ele.className.replace(reg,'');
93  return true;
94  }
95  else
96  {
97  ele.className += " "+cls;
98  return false;
99  }
100 }
101 
102 MultiSelectBox.getSelectedIndex = function(el)
103 {
104  var splits = el.id.split('_');
105  return splits[splits.length-1] | 0;
106 }
107 
108 MultiSelectBox.getSelectionArray = function(el)
109 {
110  //console.log(el.id);
111  if(el.parentElement.id.indexOf("selbox-") == 0)
112  return MultiSelectBox.mySelects_[el.parentElement.id];
113  else
114  return MultiSelectBox.mySelects_[el.getElementsByClassName("mySelect")[0].id];
115 }
116 
117 MultiSelectBox.getSelectionElementByIndex = function(el,i)
118 {
119  if(el.parentElement.id.indexOf("selbox-") == 0)
120  return document.getElementById(el.parentElement.parentElement.
121  getElementsByClassName("mySelect")[0].id +
122  "-option_" + i);
123  else
124  return document.getElementById(el.getElementsByClassName("mySelect")[0].id +
125  "-option_" + i);
126 }
127 
128 MultiSelectBox.setSelectionElementByIndex = function(el,i,selected)
129 {
130  var name = el.getElementsByClassName("mySelect")[0].id;
131  if(MultiSelectBox.isSingleSelect_[name] &&
132  selected) //if true, only allow one select at a time, so deselect others
133  {
134  var size = MultiSelectBox.mySelects_[name].length;
135  for (var opt=0; opt<size; opt++)
136  MultiSelectBox.mySelects_[name][opt] = 0;
137  }
138  MultiSelectBox.mySelects_[name][i] = selected?1:0;
139 }
140 
141 //for multiple selects to behave like checkboxes
142 MultiSelectBox.myOptionSelect = function(option, index, isSingleSelect, event)
143 {
144  var select = option.parentElement;
145  var id = select.getAttribute("id");
146  var selectList = MultiSelectBox.mySelects_[id];
147  var size = select.childNodes.length;
148 
149  if(event)
150  MultiSelectBox.dbg("Shift click = " + event.shiftKey);
151 
152  if(event)
153  MultiSelectBox.dbg("Control click = " + event.ctrlKey);
154 
155  //if shift.. then select or deselect
156  // (based on value at MultiSelectBox.lastOptSelect_[id]) from
157  // MultiSelectBox.lastOptSelect_[id]
158  // to this click
159 
160  //MultiSelectBox.dbg(selectList);
161  if (!selectList || selectList.length!=size)
162  { //first time, populate select list
163  MultiSelectBox.mySelects_[id] = [];
164  MultiSelectBox.lastOptSelect_[id] = -1;
165  selectList=MultiSelectBox.mySelects_[id];
166  for (var opt=0; opt<size; opt++)
167  selectList.push(0);
168  }
169 
170  //toggle highlighted style and global array
171  MultiSelectBox.toggleClass(option,"optionhighlighted");
172  selectList[index] ^= 1;
173 
174  if(isSingleSelect || //if true, only allow one select at a time, so deselect others
175  (MultiSelectBox.requireCtrlMultiClick_[id] && !event.ctrlKey && !event.shiftKey))
176  for (var opt=0; opt<size; opt++)
177  {
178  //fixed, now works for any order option IDs. Goes by index only.
179  var cindex = select.childNodes[opt].id.split("_");
180  cindex = cindex[cindex.length-1];
181 
182  if(cindex == index) continue;
183  else if(selectList[cindex] == 1)
184  {
185  MultiSelectBox.toggleClass(select.childNodes[opt],"optionhighlighted");
186  selectList[cindex] = 0;
187  }
188  }
189  else if(event.shiftKey &&
190  MultiSelectBox.lastOptSelect_[id] != -1)
191  {
192  //if shift.. then select or deselect
193  // (based on value at MultiSelectBox.lastOptSelect_[id]) from
194  // MultiSelectBox.lastOptSelect_[id]
195  // to this click
196 
197  var lo = MultiSelectBox.lastOptSelect_[id] < index?
198  MultiSelectBox.lastOptSelect_[id]:index;
199  var hi = MultiSelectBox.lastOptSelect_[id] < index?
200  index:MultiSelectBox.lastOptSelect_[id];
201 
202  //MultiSelectBox.dbg("lo ",lo," hi ",hi);
203  //handle multi shift click
204  for (var opt=lo; opt<=hi; opt++)
205  {
206  //MultiSelectBox.dbg(selectList[opt]," vs ",
207  // selectList[MultiSelectBox.lastOptSelect_[id]]);
208  if(selectList[opt] !=
209  selectList[MultiSelectBox.lastOptSelect_[id]]) //if not matching selected value
210  {
211  //MultiSelectBox.dbg("flip");
212  //toggle highlighted style and global array
213  MultiSelectBox.toggleClass(select.childNodes[opt],"optionhighlighted");
214  selectList[opt] ^= 1;
215  }
216  }
217  }
218 
219  MultiSelectBox.dbg(selectList);
220  selected = selectList;
221  MultiSelectBox.lastOptSelect_[id] = index; //save selection
222 }
223 
224 //This function is called by user to actually create the multi select box
225 // These parameters are optional and can be omitted or set to 0:
226 // keys, types, handler, noMultiSelect, mouseOverHandler,
227 // iconURLs, mouseDownHandler, mouseUpHandler,
228 // requireCtrlMultiClick
229 // Note: handler is the string name of the function
230 MultiSelectBox.createSelectBox = function(el,name,title,vals,keys,types,
231  handler,noMultiSelect,mouseOverHandler,iconURLs,mouseDownHandler,
232  mouseUpHandler,
233  requireCtrlMultiClick,titles)
234 {
235  if(!el)
236  { MultiSelectBox.dbg("Invalid Element given to MultiSelectBox: " + el);
237  throw new Error("Invalid Element given to MultiSelectBox: " + el); return; }
238 
239  el.innerHTML = ""; //delete current contents
240 
241  name = "selbox-" + name;
242  MultiSelectBox.addClass(el,"multiselectbox"); //add multiselectbox class to div
243 
244  MultiSelectBox.omnis_[name] = el;
245  MultiSelectBox.isSingleSelect_[name] = noMultiSelect;
246  MultiSelectBox.lastOptSelect_[name] = -1; //default to nothing selected
247  MultiSelectBox.selInitBoxHeight_[name] = 0; //init with first showing of search box in showSearch()
248  MultiSelectBox.requireCtrlMultiClick_[name] = requireCtrlMultiClick;
249 
250  //searchglass=28x28, margin=5, vscroll=16, border=1
251  var msW = (!el.offsetWidth?el.style.width.split('p')[0]:el.offsetWidth) - 28 - 5 - 16 - 2;
252  var msH = (!el.offsetHeight?el.style.height.split('p')[0]:el.offsetHeight) - 40 - 2;
253 
254  el = document.createElement("div"); //create element within element
255  MultiSelectBox.omnis_[name].appendChild(el);
256 
257  var str = "";
258 
259  if(title)
260  {
261  str += "<div id='" + name + "header' " +
262  "style='margin-top:20px;width:100%'><b>"
263  str += title;
264  str += "</b></div>";
265  }
266 
267  if(!keys) keys = vals;
268  if(!types) types = vals;
269 
270  //make selbox
271  str += "<table cellpadding='0' cellspacing='0'>";
272  str += "<tr><td>";
273  str += "<div class='mySelect' unselectable='on' id='" +
274  name + "' style='float:left;" +
275  "width: " + (msW) + "px;" +
276  "height: " + (msH) + "px;" +
277  "' name='" + name + "' " +
278  ">";
279 
280  for (var i = 0; i < keys.length;++i)//cactus length
281  {
282  str += "<div class='myOption' " +
283  "id='" + name + "-option_" + i + "' " +
284  "onclick='MultiSelectBox.myOptionSelect(this, " + i + "," +
285  noMultiSelect + ", event); ";
286  if(handler && (typeof handler) == "string") //if handler supplied as string
287  str += handler + "(this,event);"; //user selection handler
288  else if(handler) //assume it is a function
289  str += handler.name + "(this,event);"; //user selection handler
290  str += "' ";
291 
292  str += "onmouseover='";
293  if(mouseOverHandler && (typeof mouseOverHandler) == "string") //if mouseOverHandler supplied as string
294  str += mouseOverHandler + "(this,event);"; //user selection mouseOverHandler
295  else if(mouseOverHandler) //assume it is a function
296  str += mouseOverHandler.name + "(this,event);"; //user selection mouseOverHandler
297  str += "' ";
298 
299  str += "onmousedown='";
300  if(mouseDownHandler && (typeof mouseDownHandler) == "string") //if mouseDownHandler supplied as string
301  str += mouseDownHandler + "(this,event);"; //user selection mouseDownHandler
302  else if(mouseDownHandler) //assume it is a function
303  str += mouseDownHandler.name + "(this,event);"; //user selection mouseDownHandler
304  str += "' ";
305 
306  str += "onmouseup='";
307  if(mouseUpHandler && (typeof mouseUpHandler) == "string") //if mouseUpHandler supplied as string
308  str += mouseUpHandler + "(this,event);"; //user selection mouseUpHandler
309  else if(mouseUpHandler) //assume it is a function
310  str += mouseUpHandler.name + "(this,event);"; //user selection mouseUpHandler
311  str += "' ";
312 
313  if(titles)
314  str += "title='" + titles[i] + "' ";
315 
316  str += "key-value='" + keys[i] + "' type-value='" +
317  types[i] + "'>"; //index, key, ids available as attributes
318  if(iconURLs && iconURLs[i]) //add image if available
319  {
320  if(iconURLs[i][0] != '=')
321  str += "<img style='width:32px; height:32px; margin: 0px 5px -8px 0;' " +
322  "src='" +
323  iconURLs[i] + "' />";
324  else //alt text
325  str += iconURLs[i].substr(1) + " - ";
326  }
327 
328  str += vals[i];
329  str += "</div>";
330  }
331  str += "</div>";
332  //close selbox
333 
334  str += "</td><td valign='top'>";
335  //append search bar
336  str += MultiSelectBox.makeSearchBar(name);
337  str += "</td></table>";
338  el.innerHTML = str;
339 
340  if(msH > 200)
341  { //provide a minimum width for looks (to avoid long and skinny)
342  var el = document.getElementById(name);
343  if(msW < 200)
344  el.style.width = 200 + "px";
345  }
346 
347 }
348 
349 //for initializing the highlights if selects are made "manually" (without clicking)
350 MultiSelectBox.initMySelectBoxes = function(clearPreviousSelections)
351 {
352  var divs=document.getElementsByClassName('mySelect');
353  for (var el=0; el<divs.length; el++){
354  var select = divs[el];
355 
356  var id = select.getAttribute("id");
357  var options = select.childNodes;
358  MultiSelectBox.lastOptSelect_[id] = -1;
359  if (!MultiSelectBox.mySelects_[id] ||
360  MultiSelectBox.mySelects_[id].length > options.length)
361  {//if first time drawing select box OR size was reduced
362  MultiSelectBox.mySelects_[id]=[];
363  for (var opt=0; opt<options.length; opt++)
364  {
365  MultiSelectBox.mySelects_[id].push(0);
366  options[opt].setAttribute("unselectable","on");//make not selectable for ie<10
367  }
368  }
369  else
370  { //if repaint: set highlighted options
371  MultiSelectBox.dbg("repaint");
372 
373  //if more elements were added, expand the selects array
374  for (var opt=MultiSelectBox.mySelects_[id].length; opt<options.length; opt++)
375  {
376  MultiSelectBox.mySelects_[id].push(0);
377  options[opt].setAttribute("unselectable","on");//make not selectable for ie<10
378  }
379 
380  //highlight properly according to mySelects_ array
381  for (var opt=0; opt < options.length; opt++)
382  {
383  if (clearPreviousSelections)
384  MultiSelectBox.mySelects_[id][opt] = 0; //clear
385 
386  if (MultiSelectBox.mySelects_[id][opt])
387  {
388  //MultiSelectBox.dbg(opt);
389  MultiSelectBox.addClass(options[opt],"optionhighlighted");
390  }
391  else
392  MultiSelectBox.removeClass(options[opt],"optionhighlighted");
393  }
394  }
395  }
396 }
397 
398 //for searching selectboxes (works for standard "selects" and "mySelects")
399 MultiSelectBox.showSearch = function(boxid)
400 {
401  var searchBox=$(boxid+"search");
402 
403  function localPlaceSearchBox()
404  {
405  //Goal: place searchBox search input correctly in table content
406  // irregardless of user div having style.position = normal/static, absolute, relative
407 
408  //finding the search input's offset parent is not so straight forward
409  // since it is initially hidden.
410  // Use the offset parent of the content table
411  // sibling[0] := div (of content)
412  // withing div, child[0] := header, child[1] := table (of content)
413 
414  var selRect = $(boxid).getBoundingClientRect();
415  var searchOffsetParent = searchBox.parentElement.children[0].children[1].offsetParent;
416  //check if there is no offset parent (the body is the offset parent)
417  // it seems to be the case that body returns a crazy bounding client rect (left = 8 and top = 13?) .. don't understand why
418  var searchOffsetParentIsBody = searchOffsetParent == document.body;
419 
420  var offsetRect =
421  {"left": searchOffsetParentIsBody?0:
422  searchOffsetParent.getBoundingClientRect().left, //offsetLeft is different
423  "top": searchOffsetParentIsBody?0:
424  searchOffsetParent.getBoundingClientRect().top
425  };
426  //previous attempts to place search input (all fail to solve all cases):
427  //MultiSelectBox.omnis_[id].getBoundingClientRect();
428  //select.offsetParent.getBoundingClientRect();
429 
430  var offsetx = selRect.left - offsetRect.left,
431  offsety = selRect.top - offsetRect.top;
432  var margin = 5;
433 
434  searchBox.style.position="absolute";
435  searchBox.style.top=(offsety)+"px";
436  searchBox.style.left=(offsetx)+"px";
437  searchBox.style.width=(selRect.width-margin*2-30)+"px";
438  //searchBox.style.display = "block"; //for debugging position
439  } //end localPlaceSearchBox()
440 
441  if(!MultiSelectBox.selInitBoxHeight_[boxid]) //init if not yet defined
442  {
443  MultiSelectBox.selInitBoxHeight_[boxid] = $(boxid).clientHeight; //as soon as hidden is toggled H changes
444 
445  localPlaceSearchBox();
446  }
447 
448 
449 
450  //RAR decided on 2/2/2017 to not show er
451  //MultiSelectBox.toggleClass($(boxid+"searchErr"),"hidden");
452  $(boxid+"searchErr").innerHTML = "";
453 
454  if (MultiSelectBox.toggleClass(searchBox,"hidden")){
455  $(boxid).style.height = (MultiSelectBox.selInitBoxHeight_[boxid]-47) + "px";
456  $(boxid).style.paddingTop = "42px";
457  //$(boxid).childNodes[0].style.marginTop="42px";
458  //searchBox.style.left = ($(boxid).offsetLeft-8) + "px"
459  searchBox.focus();
460  MultiSelectBox.searchSelect(boxid,searchBox);
461  }
462  else{
463  MultiSelectBox.searchSelect(boxid,null, '');
464  $(boxid).style.paddingTop = MultiSelectBox.SEL_INIT_PADDING + "px";
465  $(boxid).style.height = (MultiSelectBox.selInitBoxHeight_[boxid]-10) + "px";
466  //$(boxid).childNodes[0].style.marginTop="initial";
467  }
468 }
469 
470 MultiSelectBox.searchTimeout_ = null;
471 
472 MultiSelectBox.searchSelect = function(id,el,altstr)
473 {
474  //wait 100ms so that it does not keep constantly searching as user types only when they are done typing
475  if (MultiSelectBox.searchTimeout_){
476  clearTimeout(MultiSelectBox.searchTimeout_);
477  }
478  MultiSelectBox.searchTimeout_ = setTimeout(function(){ MultiSelectBox.performSearchSelect(id,el,altstr); }, 100);
479 }
480 
481 MultiSelectBox.performSearchSelect = function(id,el,altstr)
482 {
483  var searchstr;
484  if (altstr !== undefined){
485  searchstr = altstr;
486  }
487  else{
488  searchstr = el.value;
489  }
490  MultiSelectBox.searchTimeout_ = null;
491  var select = $(id).childNodes;
492 
493  MultiSelectBox.dbg("END OF TIMEOUT FOR : "+searchstr);
494 
495  var re; //regular expression
496  $(id+"searchErr").innerHTML = "";
497  try {
498  re = new RegExp(searchstr,'i');
499  }
500  catch(err) { //invalid regular expression
501  $(id+"searchErr").innerHTML = err.message +
502  " <a href='https://regex101.com/' target='_blank'>(help)</a>";
503  re = "";
504  }
505 
506  for (var opt=0; opt < select.length; opt++)
507  {
508  var option = select[opt];
509 
510  //MultiSelectBox.dbg("opt: " + opt);
511 
512  if (option.tagName == 'INPUT') { continue; }
513  var html = option.innerHTML;
514 
515  //MultiSelectBox.dbg("tagName: " + option.tagName);
516 
517  //first set the hidden to unhidden and unbold the bolded
518  if (MultiSelectBox.hasClass(option,"hidden"))
519  MultiSelectBox.removeClass(option,"hidden");
520  else
521  option.innerHTML = html = html.replace("<b><u>","").replace("</u></b>","");
522 
523  if(searchstr == "") continue; //show all if no search str
524 
525  var text = option.textContent; //search only the text (assume that is val
526  var endOfImgIndex = html.indexOf(">");
527  var index = text.search(re);
528  var matchedText = (text.match(re) || [[]])[0]; //returns the matched string within an array or null (when null take [[]])
529  var len = matchedText.length; // so we want length of element 0
530  //MultiSelectBox.dbg(text+' '+index);
531  index = html.indexOf(matchedText,endOfImgIndex); //try to find in html text
532 
533  if(!len) //if searchstr not in option innerHTML
534  MultiSelectBox.addClass(option,"hidden");
535  else if(index != -1) //make searched string bold (if possible - must be contiguous)
536  option.innerHTML = html.slice(0,index) + "<b><u>" +
537  html.slice(index,index+len) +
538  "</u></b>" + html.slice(index+len);
539  }
540 }
541 
542 MultiSelectBox.makeSearchBar = function(id)
543 {
544  var searchBox=document.createElement("input");
545  var onchange='MultiSelectBox.searchSelect("' + id + '",this)';
546 
547  searchBox.setAttribute( "class","hidden");
548  searchBox.setAttribute( 'type','text');
549  searchBox.setAttribute( 'id',id + "search");
550  searchBox.setAttribute( "onpaste" , onchange);
551  searchBox.setAttribute( "oncut" , onchange);
552  searchBox.setAttribute( "onkeydown" , onchange);
553  searchBox.setAttribute( "onkeyup" , onchange);
554  searchBox.setAttribute( "onkeypress", onchange);
555  searchBox.setAttribute( "onselect" , onchange);
556 
557 
558  var searchErrBox=document.createElement("div");
559 
560  searchErrBox.setAttribute( "class","hidden");
561  searchErrBox.setAttribute( 'id',id + "searchErr");
562  searchErrBox.style.color="red";
563  searchErrBox.style.overflow="hidden";
564  searchErrBox.style.height="23px";
565 
566  var interval;
567  function addSearchBox()
568  {
569  var select=$(id);
570  if (select)
571  {
572  if(!MultiSelectBox.mySelects_[id])
573  MultiSelectBox.mySelects_[id] = []; //initialize to empty the selected items
574 
575 
576 
577  //err box no longer displayed...
578  // searchErrBox.style.position="absolute"; //place above title
579  // searchErrBox.style.top=(offsety - 37)+"px";
580  // searchErrBox.style.left=(offsetx + 0)+"px";
581 
582  MultiSelectBox.omnis_[id].appendChild(searchBox);
583  MultiSelectBox.omnis_[id].appendChild(searchErrBox);
584 
585 
586  clearInterval(interval);
587  }
588  }
589 
590  interval = setInterval( addSearchBox, 50);
591 
592  imgstr = "<img src='/WebPath/images/windowContentImages/multiselectbox-magnifying-glass.jpg' " +
593  " style='float:left' height='28' width='28' alt='&#128269;' ";
594  imgstr += "onclick = 'MultiSelectBox.showSearch(\"" + id + "\");' title='Search' class='searchimg'>";
595  return imgstr;
596 }
597 
598 
599