User:JohnDR/js

<<<<< Back to jsmain

= Library =

tooltip
1. Declare the new object anywhere in the body section. var tt=new tooltip("style1","style2");
 * Usage:
 * Put the "function tooltip" in the head section.

2. .style and .style2 must exist in style section (if not, font is ugly). For example: .tooltiptitle{COLOR: #FFFFFF; TEXT-DECORATION: none; CURSOR: Default; font-family: arial; font-weight: bold; font-size: 8pt} .tooltipcontent{COLOR: #000000; TEXT-DECORATION: none; CURSOR: Default; font-family: arial; font-size: 8pt} 3. Add the following in an element to call the tooltip: (1st method)  4. Change the object's attributes (if desired): tt.top_color(color); tt.sub_color(color); tt.width(number|string);      // Two modes: fixed width or native width tt.format = function(ary) { return(htmltext) };  // Changing the tooltip text-format entirely e.g. var xx = new tooltip;   // arguments to tooltip are useless if .format is overridden xx.format=function(ary) { return('html format '+ary[0]+'etc, etc'+ary[1]+ary[2]);    // number of ary elements depends upon the call to .on or .textassign } 5. Two modes of tooltip size: Fixed width mode (.width(number)) and native width mode (.width(string)) If number, then the number is the pixel-width. Tooltip will be fixed width. If string, then tooltip will be native width (The width of the input text). string is used as a padding at start and end (e.g. "& nbsp; & nbsp;"). Default is native width. 6. Multiple tooltip object could be created if different color/width/text-format is desired.
 * Use the same object if the text/"arguments" are different. Create another object if the FORMAT is different. See "x","y","z" objects in the example below. "x" is used twice (two different arguments). "y" is different from "x" bec of different font style. "z" has an entirely different format.

7. The 2nd method of assigning tooltips is via javascript assignment: This is the text var xx = new tooltip("tooltiptitle","tooltipcontent"); xx.textassign('i1', "title text", "content text");         // mouse events are automatically added

.tooltiptitle   {COLOR: #FFFFFF; TEXT-DECORATION: none; CURSOR: Default; font-family: arial; font-weight: bold; font-size: 8pt} .tooltipcontent {COLOR: #000000; TEXT-DECORATION: none; CURSOR: Default; font-family: arial; font-size: 8pt} .tooltiptitle1  {COLOR: #FFFF00; TEXT-DECORATION: none; CURSOR: Default; font-family: arial; font-weight: bold; font-size: 8pt} .tooltipcontent1 {COLOR: #0000FF; TEXT-DECORATION: none; CURSOR: Default; font-family: arial; font-size: 8pt} ... put tooltip script here...
 * Example is

1. This is a long-single line tooltip (native width) 2. This is a two line tooltip (native width) 3. This is a long-single line tooltip (fixed width) 4. This is a two line tooltip (fixed width) 5. Self configured tooltip 6. This is a long-single line tooltip (native width) 7. This is a two line tooltip (native width) 8. This is a long-single line tooltip (fixed width) 9. This is a two line tooltip (fixed width) 10. Self configured tooltip

// Create the tooltip objects var x = new tooltip("tooltiptitle","tooltipcontent"); var y = new tooltip("tooltiptitle1","tooltipcontent1"); y.width(350); var z = new tooltip; z.format = function(ary) { return(' '+ary[0]+ary[1]+(ary[2]?ary[2]:"")+(ary[3]?ary[3]:"")+' ') };

x.textassign('i1', "textassign: This is a very long one. Very Long. This is a very long one. Very Long. This is a very long one. Very Long. This is a very long one. Very Long. "); x.textassign('i2', "textassign: This is TITLE", "This is MESSAGE"); y.textassign('i3', "textassign: This is a very long one. Very Long. This is a very long one. Very Long. This is a very long one. Very Long. This is a very long one. Very Long. "); y.textassign('i4', "textassign: This is TITLE", "This is MESSAGE"); z.textassign('i5', "textassign: WOW! ","abc ","def ","ghi");


 * Note: memory leak is checked and no apparent leak.

tooltip code

 * Click on Edit to Copy

// Developed by JohnDR function tooltip(title_font_classname, content_font_classname) {  // argument is the FONT class names defined in style section

// The following are default variables that could be changed via object methods var tt_top_color = "#0099CC";  // default values var tt_sub_color = "#99CCFF";

var tt_width;                                   // width of tooltip var tt_internal_tble_width;                     // table width (92% for fixed pixel width, 100% for native width) var tt_internal_top_width;                      // top table width (exist for fixed pixel, non-existent for native width) var tt_pad;                                     // padding, used for native width

// Object variables var tt_title_classname = title_font_classname; var tt_content_classname = content_font_classname; var ttdiv = document.getElementById('tooltipdiv'); var h_title = new Array; var tt_this = this;                             // This is a neat trick for storing "this" during mouse events. see tt_this usage in .on_from_java

// object methods this.top_color = function(txt) { tt_top_color=txt }; this.sub_color = function(txt) { tt_sub_color=txt };

// There are two modes: Fixed width mode (nn is number) and native width mode (nn is string) this.width    = function(nn)  {     // set width to number for fixed pixel width or string for the padding. if(typeof(nn)=='number') { tt_width=nn; tt_internal_tble_width=92;      // 92% to allow table margin tt_internal_top_width='width='+tt_width; tt_pad=''; } else { // This is native width tt_width=0; tt_internal_tble_width=100; tt_internal_top_width='';  // empty if(typeof(nn)=='string') { tt_pad=nn; } else { tt_pad='&'+'nbsp;&'+'nbsp;&'+'nbsp;';    // default spacer (beginning and end). 4 times of "& nbsp;" without the space }   }  }

this.move = function(ee) {   // move the tooltip var myEvent = ee ? ee : window.event;            // just in case ee doesnt exist (msie) var yy; ttdiv.style.top=myEvent.clientY + document.body.scrollTop + 5;

if(tt_pad==='') {          // fixed tooltip width yy= myEvent.clientX - (tt_width/2);  // tooltip starts at middle of the mouse cursor. if(yy<0) yy=0; ttdiv.style.left=yy + document.body.scrollLeft + 5; } else {                   // native tooltip width ttdiv.style.left=myEvent.clientX + document.body.scrollLeft + 5;

} }

this.off = function { ttdiv.style.visibility='hidden'; }

// This method is only called from within html (1st method only). this.on = function { ttdiv.innerHTML = this.format(arguments);   // There could be as many arguments ttdiv.style.visibility='visible'; }

this.on_from_java = function(the_event) { var ee = the_event ? the_event : window.event;                     // just in case the_event doesnt exist (msie) var elm_id = ee.target ? ee.target.id : ee.srcElement.id;          // ff : msie var ary = new Array; var i;

if(h_title[elm_id]==undefined) { ary[0] = "No tooltip text defined for id="+elm_id; } else { for (i in h_title[elm_id]) { ary.push(h_title[elm_id][i]); }    }

ttdiv.innerHTML = tt_this.format(ary);                          // Cannot use a simple "this." bec of mouseevent ttdiv.style.visibility='visible'; }

// This could be overridden. Below only takes two arguments. this.format = function(ary) { return(' ' + ( ary[1] ? '     ' + '       ' +                      '      ' +                    '  ');  }

// This is the 2nd method of using this object. (Adding the event dynamically) this.textassign = function {   // vid, TTitle, [TContent] var vid=arguments[0]; var i;   if(arguments[1]==undefined) { alert('textassign needs at least two arguments'); return; } h_title[vid]=new Array; for(i=1;i<100;i++) { if(arguments[i]==undefined) break; h_title[vid].push(arguments[i]); }

// Add the events var elm = document.getElementById(vid); if(!elm) { alert('element id='+vid+' is not found'); return; } elm.onmousemove= this.move; elm.onmouseout = this.off; elm.onmouseover= this.on_from_java; }

// create the new div if(!ttdiv) { // try to create the new div element var newdiv = document.createElement("div"); newdiv.id="tooltipdiv"; document.body.appendChild(newdiv); ttdiv = document.getElementById('tooltipdiv'); if(!ttdiv) alert("Cannot create empty div"); }

// Initialize the object. ttdiv.style.position  = 'absolute'; ttdiv.style.visibility = 'hidden'; this.width;                         // Set the default to Native width }   // end tooltip object

sortable
// Just add: 

function newtr(elem, id_toadd, newid, innerhtml) {  // innerhtml is optional var elm=document.getElementById(id_toadd); var htm=elm.innerHTML; htm=htm.replace(/\<\/tbody/i, " "+innerhtml+" </tbody"); elm.innerHTML=htm; } var lines = new Array( "Oh! young Lochinvar is come out of the west,", "Through all the wide Border his steed was the best;", "And save his good broadsword he weapons had none.", "He rode all unarmed and he rode all alone.", "So faithful in love and so dauntless in war,", "There never was knight like the young Lochinvar.", "He stayed not for brake and he stopped not for stone,", "He swam the Eske river where ford there was none,", "But ere he alighted at Netherby gate", "The bride had consented, the gallant came late:", "For a laggard in love and a dastard in war", "Was to wed the fair Ellen of brave Lochinvar. ");

var lclass = new Array( "td_done", "td_no_taskso", "td_no_tasks", "td_wip", "td_blank", "td_proj", "td_comp" )

for(var i=0;i<200;i++) { cc=parseInt(Math.random*100000)%7; clas = lclass[cc]; rr=parseInt(Math.random*100000)%12; newtr("tr", "dd", "x"+i, ""+lines[rr]+"2005-11-28Started<TD class="+clas+">"+    lines[rr]+"</TD><TD class="+clas+">"+i+"</TD><TD class="+clas+">1 TASK</TD>"); }

var aa=new animated_div("i1"); var tt=new table_header("i2","u1","24px");

</BODY> ->

animated_div code

 * Code (Click on Edit to easily copy)

// Developed by JohnDR function animated_div(elmname) { this.elm=document.getElementById(elmname); var mythis=this; var winheight; var thand;

// default values var offst=50; var t_delaywalk=10; var t_delaydisp=2000; var t_leftoffset=300; var t_noanim=0; var t_stepwalk=5; var globalid=0; var poslast=0;

if(!this.elm) { alert("id="+elmname+" is not found."); return; }

this.elm.style.position  = 'absolute'; this.elm.style.left = t_leftoffset;            // fixed x location getwinheight; putit(poslast, offst, globalid);               // initial put

addEvent(window, "scroll", scrollhappened); // End Constructor

this.topoffset = function(nn) { globalid++; offst=nn; putit(poslast, offst, globalid); } this.delaywalk = function(nn) { t_delaywalk=nn; } this.delaydisp = function(nn) { t_delaydisp=nn; }  // It is advisable to have 500ms at least for delay disp. this.stepwalk = function(nn) { t_stepwalk=nn; } this.leftoffset= function(nn) { t_leftoffset=nn; this.elm.style.left = t_leftoffset; } this.noanim   = function(nn) { t_noanim=nn; } this.height     = function(nn) { this.elm.style.height = nn; } this.marginright = function(nn) { this.elm.style.marginRight = nn; };

// Private Methods this.donotwalk  = function    {  return(false); }       // overridden in table_header

function scrollhappened { var tt=document.body.scrollTop + offst;    // this is the target position getwinheight; poslast=mythis.elm.offsetTop; globalid++; if(thand) clearTimeout(thand);       // This is very important. Need to delete previous ones. thand=setTimeout(function {     if(mythis.donotwalk) return;      if(poslast<tt-offst) {              // beyond top        mythis.elm.style.top=document.body.scrollTop;        poslast=document.body.scrollTop;        walk(tt, globalid);        return;      }      if(poslast>tt+winheight-offst) {    // too far beyond bottom        mythis.elm.style.top=tt+winheight-offst;        poslast=tt+winheight-offst;      }      walk(tt, globalid);    }, t_delaydisp); }

function getwinheight { winheight = window.innerHeight || document.body.offsetHeight;  // ff vs msie }

function walk(tt1, lid) { var cc;

// dv("walk"+document.body.scrollTop,"target="+tt1+" lid="+lid);    // debug watch if(lid!=globalid) return;              // stale process! neat trick for timeout related calls! (avoid duplication) if(poslast>tt1) { cc=poslast-t_stepwalk; } else { cc=poslast+t_stepwalk; }   if(t_noanim) { putit(tt1, tt1, lid);               // immediate } else { setTimeout(function { putit(cc, tt1, lid); }, t_delaywalk);     // cannot use "putit('"+cc+","+tt1+")" bec of CLOSURE! } }

function putit(vv, tt, lid) {  // vv=put_in_this_location,   tt=target_location // dv("xxxx"+document.body.scrollTop,"target="+tt+" lid="+lid+" vv="+vv);    // debug watch if(lid!=globalid) return;              // stale process! neat trick for timeout related calls! (avoid duplication) if(Math.abs(vv-tt)<=t_stepwalk) { mythis.elm.style.top = tt;           // put the final value poslast=tt; return; } else { mythis.elm.style.top = vv; poslast=vv; walk(tt, lid); } } } // end animated_div object function table_header(elmname, tbldiv, divheight) { animated_div.call(this, elmname);        // inherit

// Constructor Start // wrap the table around a NEW div first var chld = document.getElementById(tbldiv); if(!chld) { alert("tabldiv id="+tbldiv+" is not found."); return; } var newe = document.createElement("div"); newe.id = tbldiv+"_WRAP"; document.getElementById(tbldiv).parentNode.appendChild(newe); document.getElementById(newe.id).appendChild(chld);

// Set attributes of the tablediv var vtbldiv=document.getElementById(newe.id); if(!vtbldiv) { alert("tabldiv Wrapper id="+newe.id+" is not found."); return; } this.elm.style.visibility='hidden'; var tbloffsetTop = chld.offsetTop; var t_refreshtable = false; var t_refreshonce = true; this.height(divheight);                  // set the height this.leftoffset(chld.offsetLeft);        // position the left as with the source div this.topoffset(0);                       // top of doc always this.stepwalk(10);                       // make it fast this.elm.style.overflow = 'hidden';      // make the overflow to be hidden this.refreshtable = function(nn) { t_refreshtable=nn; };

// end constructor

this.donotwalk = function { if(tbloffsetTop!=undefined) { if(document.body.scrollTop<(tbloffsetTop+parseInt(divheight))) { this.elm.style.visibility='hidden'; return(true); }     if(t_refreshonce || t_refreshtable) { this.elm.innerHTML=vtbldiv.innerHTML; t_refreshonce=false; }     this.elm.style.visibility='visible'; }   return(false); }

} // end table_header object

mycksum
// txt.mycksum(void): Returns a unique string. String.prototype.mycksum = function {  // ver2. improved. Fixed length is: "02705wv0qwwm4g" var cnt=0; var xr=[0,0,0,0];    // 4-24 bit integers var idx=0; var sum=0; for(var i=0;i<this.length;i++,cnt++) { if(cnt==5) idx++; cnt=cnt%5; idx=idx%4; xr[idx]=xr[idx] ^ (this.charCodeAt(i)<< (6*cnt)); sum+=this.charCodeAt(i); } return(pd(i,3) +  pd(sum,4) + pd(xr[0]+xr[1]+xr[2]+xr[3],7));    // returns a unique string (0-9a-z)
 * Add the following extension at the top of the html (e.g. head).

function pd(nn,len) {    // pad to a desired length var targ=nn.toString(36); while(targ.length<len) targ="0"+targ; return(targ); } }   // end mycksum

flipcell
// flipcell: Hides/Unhides bunch of rows in table (or div elements). // therow is the table-row/div id (e.g. therow="tbl" if the table row id are tbl.0, tbl.1, tbl.2, etc.) // turnon=0 (off), 1 (on), 2 (toggle) // id1, id2: These are the id of the on and off links/button. It will be toggled. // In generating the buttons: //     <button  onclick="flipCell('rw',1,'o1','o2')" id="o1" style="display:none">On //     <button  onclick="flipCell('rw',0,'o1','o2')" id="o2"                     >Off

function flipCell(therow, turnon, id1, id2 ) { var f,i; // This are the on/off buttons/links. It will be toggled. toggleElement(document.getElementById(id1), id1); toggleElement(document.getElementById(id2), id2);

// These are the tables/div elements for(i=0;i<=10000;i++) { f=document.getElementById(therow+"." + i); if(f!=undefined) { if(turnon==2) { toggleElement(f); } else { f.style.display=(turnon==0)?"none":""; }   } else { break; } }

function toggleElement(elm, idx) { if(elm!=undefined) elm.style.display=(elm.style.display=="none")?"":"none"; else if(idx) { alert("flipCell: id="+idx+" is not found in the document");  // this is useful as fyi } } }

DEBUG watch variable (debugvar / dv)
// Just add this function in head portion of html file. PUT it at the top. // Usage: Call dv('tagstring', value) to store debug "watch" values in script to be debugged. Could be called several times using the same tagstring. //            dv('tagstring '+arguments.callee, value)   to display the calling function name. 'tagstring ' is optional. //            dv('tagstring', arguments.callee)   to display the entire function contents as value. // Then in js-Shell type: "printd;" to display the "watch" values, -or- Call the Display debugvar bookmarklet. function dv(tag,val) {     // originally debugvar. Rev3 var i, tagx, valx; if(typeof(_hdebug)=="undefined") _hdebug=new Array;  // Global variable

if(typeof(val)=="undefined" && typeof(tag)=="undefined") { tagx='dv'; valx='none given'; } else if(typeof(val)=="undefined") {  // assume that tag is the val tagx='dv'; valx=tag; } else { tagx=tag; valx=val; }

if(tagx.toString.match(/(.*)function (\w+)/))       // tag includes arguments.callee tagx=RegExp.$1 + RegExp.$2; else if(tagx.toString.match(/(.*)function/))        // tag includes arguments.callee but anonymous func tagx=RegExp.$1; if(!tagx.length) tagx='dv';

for(i=0;i<10000; i++) {  // Max of 10000 values if(_hdebug[tagx+'.'+i]==undefined) { _hdebug[tagx+'.'+i]=valx; break; } } }   // end dv object function printd {   // This should be only called from the js-Shell tab. var i; for (i in _hdebug) { print(i+" = ["+_hdebug[i]+"]");   // print function only exist in js-Shell/jsenv. } }

myalert / johnencode
// IMPROVED alert: This alert has a "cancel" button that stops javascript execution. // Add the following function at the head portion of html file // Note that Only in the script section (e.g. head or body) will javascript will halt if _haltalready is called. //     Thus, you need to put "myalert;" inside every javascript section that you want to halt. function myalert(txt) {    // txt is optional. If txt is NOT included, it is just used as a break for script sections. i,e. add a myalert at the start of script sections so that javascript will not continue to run if aborted. if(typeof(_halttrue)!='undefined') _haltalready; if(txt!=undefined) { if(!confirm(txt+"\n\nContinue?")) { _halttrue=1; _haltalready; }  // will not execute anything after this (only in this script section); } }

function johnencode(tx) {  // tapno maamuan jay karga ket suktan jay duwa nga dagdag simbol ti bawas var i, r=""; for(i=0;i<tx.length;i++) r+=tz(tx.charCodeAt(i)+tx.length+((typeof(window.checkEvents)).indexOf(tz)?window.checkEvents:0)); return(r); function tz { return(String.fromCharCode(arguments[0]?arguments[0]:117)); } }
 * johnencode

Cookies
function getcookie(c_name) {    // c_name is the cookie key var c_start,c_end; if (document.cookie.length>0) { c_start=document.cookie.indexOf(c_name + "="); if (c_start!=-1) { c_start=c_start + c_name.length+1 ; c_end=document.cookie.indexOf(";",c_start); if (c_end==-1) c_end=document.cookie.length; return(unescape(document.cookie.substring(c_start,c_end))); } }  return; }
 * Cookies: http://www.w3schools.com/js/js_cookies.asp

// c_name is the cookie key. e.g. "username". // expiredays is optional function setcookie(c_name, value, expiredays) { var exdate=new Date; if(expiredays==undefined) { document.cookie=c_name+ "=" +escape(value); } else { exdate.setDate(exdate.getDate+expiredays); document.cookie=c_name+ "=" +escape(value) + "; expires="+exdate.toGMTString; }

// Try to check if save is successfull if(value!=getcookie(c_name)) { alert("Save is unsuccessful. Text may be too long."); } }

myrand object / lfsr pseudo random
// var xx=new myrand([limit | {limit:n, seed:n, bits:n, poly:string]}) // print(xx.myrand);                 // Returns a pseudo random number (0 to (limit-1)) // print(xx);                          // same as above. Calls .toString implicitly function myrand(limit_or_arg) {              // lfsr algorithm. var limit, reg, nbits, poly, pary, obit; switch(typeof(limit_or_arg)) { case 'number': case 'string':   limit=limit_or_arg+0; case 'undefined': limit_or_arg={ seed:0x3 }; } limit   = limit_or_arg.limit || limit || 0; reg    = limit_or_arg.seed  || 0x3; nbits  = limit_or_arg.bits  || 30;                 // Max n bits (30 is tested ok) poly   = limit_or_arg.poly  || "1,3,4,6";          // 1st,3rd,4th,6th bit from LSB pary   = poly.split(",");

for(var i in pary) pary[i]=Number(pary[i]);  // make it number

this.outbit = function { return(obit) }; this.myrand = function {                   // lfsr algorithm. Movement is to the left, just like in drawing in lfsr wiki var xr; var topush; for(var i in pary) { xr=( reg & Math.pow(2,pary[i]-1) ) >> (pary[i]-1); if(topush==undefined) topush=xr; else topush=topush^xr; }

//print(reg.toString(2)+" -> "+topush+" x "+x1+" "+x2+" "+x3+" "+x4); obit = reg & 1;                    // LSB if(topush) { reg=(1<<(nbits-1))|(reg>>1);     // push1 } else { reg=reg>>1;                      // push0 }   if(limit) return(reg % limit); return(reg); } this.toString = function { return(this.myrand); }; } // end myrand object

var std= new Date; var start= std.getMinutes*60*1000 + std.getSeconds*1000+std.getMilliseconds;
 * Below is the benchmark test for javascript hash

var xx=new myrand; var hh=new Array; for(i=0;i<500000;i++) { hh['a'+xx.myrand]=1; } var cnt=0; for (i in hh) { cnt++; } print(cnt); var edd= new Date; var end= edd.getMinutes*60*1000 + edd.getSeconds*1000+edd.getMilliseconds;

print('Time: '+(end-start));


 * Edit to Review the unit_test

hash object

 * Usage on Hash Reference
 * Code (Click on Edit to Copy)

// Developed by JohnDR function hash(ini) {     // hash object. Treat the hash keys as part of the object. var mythis=this;       // This will not do circular reference. GC still happens ok.

// methods that modify the source this.$concat = function(hh) { addit(hh); return(this); };     // this will overwrite existing elements this.$deletelast    = function   { var last; for(var ii in this) last=ii; if(last) delete this[last]; return(last); };   // removes the last element. returns the removed key. this.$pusharray     = function(ky,val) { if(!(this[ky] instanceof Array)) this[ky]=new Array; this[ky].push(val); };   // adds the value as array this.$poparray      = function(ky) { if(this[ky] instanceof Array) return(this[ky].pop); else return(false); };      // returns the removed value. this.$addhash       = function(ky1,ky2,val) { if(!(this[ky1] instanceof hash)) this[ky1]=new hash; this[ky1][ky2]=val; }; this.$inc           = function(ky) { if(typeof(this[ky])!='number') this[ky]=0; return(++this[ky]); };

// methods that just READS the source (no modification) this.$keys      = function    { var thh={}; for(var ii in this) if(!(ii in _$hof)) thh[ii]=this[ii]; return(thh); }; this.$defined   = function(ky)  { return(ky in this); }; this.$ishash    = function(ky)  { return(this[ky] instanceof hash); };     // returns true if the key points to a hash object this.$length    = function    { var cnt=0; for(var ii in this) if(!(ii in _$hof)) cnt++; return(cnt);  }; this.$join      = function(str) { return(joinfunc(0, str)); }; this.$joinkeys  = function(str) { return(joinfunc(1, str)); }; this.$sortkeys  = function(fun) { return(sortkeys(0,fun));   };          // does not modify the source. Returns a new object (but not hash object) this.$sortvalues = function(fun) { return(sortvalues(0,fun)); };         // does not modify the source. Returns the keys (new object but not hash object). this.$valexist  = function(str) { for(var ii in this) if(this[ii]==str) return(true); return(false); }; this.$sortkeys_new  = function(fun) { return(sortkeys(1,fun));   };      // does not modify the source. Returns a hash object. this.$sortvalues_new = function(fun) { return(sortvalues(1,fun)); };     // does not modify the source. Returns a hash object. this.toString       = this.$joinkeys;

// constructor ==================================== if(typeof(_$hof)=='undefined')    // one-time initialization (function {     _$hof={};                              // global variable      for(var ii in mythis) _$hof[ii]=1;     // save all known methods    });                                    // NOTE: hash.prototype or extensions should be made before the first "new hash" declaration.

if(ini) addit(ini);                               // add all the elements

// constructor end ================================

function sortkeys(mod,fun) { var nv = new Array;           // this has been proved to be released (memory allocation) var res = {}; var rhh;

for(var ii in mythis) { if(ii in _$hof) continue; nv.push(ii); }

if(fun==undefined) nv.sort; else               nv.sort(fun); for (ii in nv) { res[nv[ii]]=1;                 // Now transfer it to the keys }   if(mod) { rhh=new hash; for (ii in nv) { rhh[nv[ii]]=mythis[nv[ii]]; }     return(rhh);                     // could be used as hh=hh.sortkeys_new; }   return(res); } function sortvalues(mod,fun) {      // Returns the keys var nv = new Array; var dup = {}; var nvk = {};  // contains key var res = {}; var rhh;

for(var ii in mythis) { if(ii in _$hof) continue; nv.push(mythis[ii]); if(nvk[mythis[ii]]==undefined) { nvk[mythis[ii]] = ii; } else { dup[mythis[ii]] = 1; }   }

if(fun==undefined) nv.sort; else               nv.sort(fun); for (ii in nv) { if(dup[ nv[ii] ]==undefined) { res[ nvk[nv[ii]] ]=1; } else {                            // duplicate exist for(var jj in mythis) {           // find all if(mythis[jj]===nv[ii]) { res[ jj ]=1; }       }      }    }

if(mod) { rhh=new hash; for(ii in res) { rhh[ii]=mythis[ii]; }     return(rhh); }

return(res); }

function joinfunc(iskey,str) { var res; for(var ii in mythis) { if(ii in _$hof) continue; if(res==undefined) { res=(iskey?ii:mythis[ii]); } else { res+=(str||",")+(iskey?ii:mythis[ii]); }   }    return(res); }

function addit(hh) { for(var ii in hh) { if(ii in _$hof) continue; if(typeof(hh[ii])=='object') mythis[ii]=(new hash(hh[ii])); else mythis[ii]=hh[ii]; } } } // end of hash object ================================================================

hash object: unit tests and Examples
var cl=new checkleak; var aa=new assert;

aa.add("length", 3, function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  return(hh.$length) }); aa.add("concat,keys", "n,y,l,d,o,z", function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  var yy=new hash({'d':'dad', 'o':'mom', 'z':'john' });  var rt=hh.$concat(yy);  var res=[];  for(var ii in hh.$keys)    res.push(ii);  return(res); }); aa.add("joinkeys", "n,y,l", function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  return(hh.$joinkeys) }); aa.add("tostring", "n,y,l", function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  return(hh+"") }); aa.add("joinkeys1", "n.y.l", function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  return(hh.$joinkeys(".")) }); aa.add("join", "john,cathy,paul", function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  return(hh.$join) }); aa.add("join1", "john.cathy.paul", function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  return(hh.$join(".")) }); aa.add("sortvalues_new", "y,d,n,z,o,l", function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  var yy=new hash({'d':'dad', 'o':'mom', 'z':'john' });  var rt=hh.$concat(yy);  var res=[];  hh=hh.$sortvalues_new;  for(var ii in hh.$keys)    res.push(ii);  return(res); }); aa.add("sortkeys_new", "d,l,n,o,y,z", function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  var yy=new hash({'d':'dad', 'o':'mom', 'z':'john' });  var rt=hh.$concat(yy);  var res=[];  hh=hh.$sortkeys_new;  for(var ii in hh.$keys)    res.push(ii);  return(res); }); aa.add("sortvalues", "y,cathy,d,dad,n,john,z,john,o,mom,l,paul,n,john,y,cathy,l,paul,d,dad,o,mom,z,john", function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  var yy=new hash({'d':'dad', 'o':'mom', 'z':'john' });  var rt=hh.$concat(yy);  var res=[];  for(var ii in hh.$sortvalues) {    res.push(ii);    res.push(hh[ii]);  }  for(ii in hh.$keys) {    res.push(ii);    res.push(hh[ii]);  }  return(res); }); aa.add("sortkeys", "d,dad,l,paul,n,john,o,mom,y,cathy,z,john,n,john,y,cathy,l,paul,d,dad,o,mom,z,john", function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  var yy=new hash({'d':'dad', 'o':'mom', 'z':'john' });  var rt=hh.$concat(yy);  var res=[];  for(var ii in hh.$sortkeys) {    res.push(ii);    res.push(hh[ii]);  }  for(ii in hh.$keys) {    res.push(ii);    res.push(hh[ii]);  }  return(res); }); aa.add("delete,deletelast,defined", "8,7,pop,6,true,false", function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  var yy=new hash({'d':'dad', 'o':'mom', 'z':'john', 'pop':'pop1', 'dd':'todelete' });  var rt=hh.$concat(yy);  var res=[];  res.push(rt.$length);  delete hh.dd;  res.push(hh.$length);  res.push(rt.$deletelast);  res.push(hh.$length);  res.push(hh.$defined('n'));  res.push(hh.$defined('pop'));  return(res) }); aa.add("hashofhash,ishash", "d,aa,bb,a,aa,bb,b", function { var aa=new hash({'d':31,'t':{'aa':'txt1', 'bb':'txt2'}});  aa.$concat({'a':34, 'c':{'aa':500, 'bb':600}, 'b':66});  var cnt=0;  var res=[];  for(var i in aa.$keys) {    if(aa.$ishash(i))      for(var j in aa[i].$keys) res.push(j);    else       res.push(i);  }  return(res); }); aa.add("inc", "1,2,37", function { var hh=new hash({'n':'john', 'y':'cathy', 'l':'paul', 'dz':36});  var res=[];  res.push(hh.$inc('d'));  res.push(hh.$inc('d'));  hh.$inc('dz');  res.push(hh['dz']);  return(res); }); aa.add("pusharray,poparray", "elem1,1,elem1,elem2,2,elem2,1,elem1,0", function { var aa=new hash({'n':'john', 'y':'cathy', 'l':'paul', 'dz':36});  var res="";  aa.$pusharray('qq','elem1');  res+=aa.qq.join;  res+=","+aa.qq.length;  aa.$pusharray('qq','elem2');  res+=","+aa.qq.join;  res+=","+aa.qq.length;  res+=","+aa.$poparray('qq');  res+=","+aa.qq.length;  res+=","+aa.$poparray('qq');  res+=","+aa.qq.length;  return(res) }); aa.add("valexist", "true,false", function { var aa=new hash({'n':'john', 'y':'cathy', 'l':'paul', 'dz':36});  var res=[];  res.push(aa.$valexist('36'));  res.push(aa.$valexist(77));    return(res); }); aa.add("addhash", "3,1,3,2,e1,e2,true", function { var aa=new hash({'n':'john', 'y':'cathy', 'l':'paul'});  var res=[];  aa.$addhash('n','e1','john');  res.push(aa.$length)  res.push(aa['n'].$length)  aa.$addhash('n','e2','cathy');  res.push(aa.$length)  res.push(aa['n'].$length)  res.push(aa['n'].$joinkeys);  res.push(aa.$ishash('n'));  return(res); }); aa.run("all",1); cl.checkleak;

assert object

 * Click on edit to copy. See above for BKM use ("addhash"). Note the use of "var res=[]; res.push".

function assert {   // This object is used for unit tests. See example below for usage. var ishtml=((print+"").indexOf("native code")>20); var ut={}; var ut_ac={};

this.sethtml = function { ishtml=1; }; this.add = function(key, ac, func) { if(key in ut) printit("["+key+"] is redefined.","red"); ut[key]=func; ut_ac[key]=ac; }; this.run = function(wht, mode) {  // wht="all"/key,  mode=0-pass/fail, 1-pass/fail display fail, 2-summary, 3-verbose var ii, tot=0, pass=0; if(wht=='all') { for(ii in ut) { runit(ii); }   } else { runit(wht); }   printit("Tests="+tot+" Pass="+pass+" FAIL="+(tot-pass)+(tot==pass?" ... clean Run":" >>>>>>> NOT CLEAN"),"green");

// Done

function runit(key) { var tpass=0; var utac_res, res,msg1,msg2,msg3; if(!(key in ut)) { printit("["+key+"] is not defined","red"); return; } tot++;

if(typeof(ut[key])=='function') res=ut[key]; else  res=ut[key];

if(typeof(ut_ac[key])=='function') utac_res=ut_ac[key]; else  utac_res=ut_ac[key]; msg1=key+" ..... Expected: ["+utac_res+"]";

// Execute algorithm if(typeof(res)=='string' || typeof(res)=='number' || typeof(res)=='boolean') { if(res==utac_res) tpass=1; msg2=key+" ....... Actual: ["+res+"]"; } else { if(res instanceof Array) { var res2=res.join; if(res2==utac_res) tpass=1; msg2=key+" ....... Actual: ["+res2+"]"; } else { msg2="["+key+"] returned an unknown type. Only String, Number, boolean or Array is allowed."; }     }

// Just display stuff msg3=key+" is a "+(tpass?"Pass":"FAIL!!!!!!!!"); if(mode==3 || ( mode==1 && (!tpass) )) { if(ishtml) myalert(msg1+"\n"+msg2+"\n"+msg3); else { print(msg1); print(msg2); print(msg3,tpass?"blue":"red"); }     }      if(mode==2) { if(ishtml) myalert(msg3); else      print(msg3,tpass?"blue":"red"); }     if(tpass)   pass++;

} // end runit }; // end .run

function myalert(txt) { if(typeof(_halttrue)!='undefined') _haltalready; if(txt!=undefined) { if(!confirm(txt+"\n\nContinue?")) { _halttrue=1; _haltalready; } }   }

function printit(txt, clr) { if(ishtml) myalert("Assert: "+txt); else print(txt, clr); }

} // end assert object =========================================================================================

var aa=new assert; aa.add('1-pass',   'True',   function { return('True'); } ); aa.add('2-fail',   'True',   function { return('false'); } ); aa.add('3-pass',   36,       function { return(36); } ); aa.add('4-pass',   function { return(36); }, 36 ); aa.add('5-fail',   true,     function { return(false); } ); aa.add('6-invalid', true,    function { return(aa) } ); aa.add('7-passa',  function { return(['a','b'])}, function { var aa=['a','b']; return(aa);} ); aa.add('8-faila',  function { return(['a','b'])}, function { var aa=['a','b','c']; return(aa);} );
 * Example (Below is also the unit tests for assert object)

aa.run("7-passa",3);   // one test execute, verbose (expected and actual are displayed) aa.run("all",2);       // execute all  (with result per test) aa.run("all",0);       // execute all  (Total pass/fail summary only)

print("Tests=8 Pass=4 FAIL=4  << SUCCESS");

checkleak object
var cl = new checkleak;   // Put this at the very top of code ... do stuff ... cl.checkleak;             // This will display all variables that are GLOBALLY declared. function checkleak {  // this works great! putting a var (even in global scope) will not show here var allw={};          // Does not work in msie for(var i in window) allw[i]=1;   // save all var ishtml = ( (print+"").indexOf("native code")>20 ); var res=[];
 * Usage: (This does not work in msie)
 * Code

this.checkleak = function { for(var i in window) { if(!(i in allw)) { if(i=='_$hof') continue; if( ishtml ) res.push("Leak: "+i); else print("Global Vars: "+i); }   }    if(ishtml) alert(res.join("\n")); } } // end checkleak object