/* 
   Javascript library by:  Raul Parolari, 2006-2007. 

   Contains utilities functions grouped under each pertinent SECTIONS:
   1) Debug,  Warnings, Browser Validation
   2) Window Manipulation 
   3) Search: search for  elements with certain characteristics
   4) Dynamic Visibility
   5) Form Validation and Processing
   6) Own Email Hiding
*/

var lib_debug = 0;

     /***************      SECTION 1: Debug, Warnings    *****************/

function lib_activate_debug() { 
   lib_debug = 1; 
}

     // Functions to verify: BROWSER Compatibility with DOM
     //                      Html To JavaScript interface

// do not clutter code with alerts & co. Abstract error handling
function lib_warn_no_element(id, type) {

   type = type ? type : "";     // prevent 'undefined' in alert
   alert("there is no " + type + " element with id/name " + id 
         + "; review html!");
}

function lib_warn_browser(problem) {

   problem = problem ? problem : "may not be W3c compliant.";

   alert(  "your browser " + problem + "\n" 
         + "this page cannot work with all features as intended");
}

function lib_test_browser_w3c() {

   if (! document.images) {
      warn_browser("browser is not even at Level0 Dom!"); 
      return false;
   }

   if ( ! document.getElementById ) { 
      warn_browser("is no W3C compliant");
      return false;
   }
   return true;
} // lib_test_browser_w3c()

function lib_stringify_elements(elems) {
   var string = "";    // init to empty, so won't be undef 1st time
   for (var i=0; i < elems.length; i++) {
      var el = elems[i];
      string += el.name + "= " +el.value + ", type= " + el.type + "\n"; 
   }
   return string;
}

/* This function searches for an element with a certain id. The 'type' parameter
   is optional, being passed by the caller in order to add debugging details
   in case the element is not found.
*/
function lib_fetch_element(id, type)  {     
   // type is optional (for info)
   type = type ? type : "";    // avoid undefs

   var elem = document.getElementById(id);

   if (! elem) { lib_warn_no_element(id, type); }

   return elem;
} // lib_fetch_element()


function lib_check_args(func_name, nr_received, nr_expected) {
	
   if (nr_received == nr_expected) return true;

   // validation failed
   alert(func_name + ': got ' + nr_received + 'paramaters, expected ' 
                               + nr_expected);
   return false;

} // check_nr_arguments


/*
   The Dom has a method to insert a node as a child of the parent node; the
   position is the following:
   - parent.appendChild(newEl) puts newEl as last node of the parent
   - parent.insertBefore(newEl, el_ref) places newEl before el_ref
   We want a routine to insert an element AFTER a certain reference.

                                parent 
                                  | 
                                  |------ refNode_X
               before Y  ----->   |
                                  |------ refNode_Y
       we want: after Y  ----->   |
                                  |------ refNode_Z

        parent.insertBefore(newNode, refNode_Y);   // DOM method

        lib_insertAfter(newNode, refNode_Y);     // our function 
*/
function lib_insertAfter(newElement, targetElement) {
   /* 
       if targetEl is the last child of its parent, append newEl as child
   */
   var parent = targetElement.parentNode;

   if (parent.lastChild == targetElement) {
      parent.appendChild(newElement);
   }
   else {

      /* targetEl not the last child; thus, append newEl before sibling */
      parent.insertBefore(newElement, targetElement.nextSibling);
   }
} // end lib_insertAfter()


/* this is not useful, but just use it once, to see what a form is made of */
function show_properties_form(form_id) {
   var form = document.getElementById(form_id);
   var str = "";
   for (name in form) {
      /* skip at least functions, of a long bloody list */
      if (typeof form[name] == "function") continue;
      str +=  name + "= " + form[name] + '\\n';  
   }
   alert(str);
} 

     /***************      SECTION 1:  Window management   *****************/

// links with class (usually "new_win" in html) get to open new window
function lib_attach_openwinHandler_to_links_with_class(klass) {

   var links;
   links = document.getElementsByTagName("a");
   links = lib_find_subset_class(links, klass);
   for (var i=0; i < links.length; i++) {

      var link = links[i];
      // if (lib_debug && i==0) alert("xxx:" + link);

      if (lib_debug) alert("setting newWindow for link " + link.href);

      // [see the OLD version (at the end of file) for a different version]
      link.onclick = function() {
         var link = this;
         var size = link.className.match(/small/)  ? "small" : 
                    link.className.match(/large/)  ? "large" : "medium";
         lib_open_win(link.href, size);
         return false; // prevent browser to follow the link (else we open
                       // the new page in the calling window too)
      }
   } 
} // lib_attach_openwinHandler_to_links_with_class() 


function lib_open_win(url, size) {
   // some arbitrary decisions (a better interface in future) on properties

   var const_props =    "menubar=yes,toolbar=yes,location=yes,resizable=yes," 
                      + "status=yes,scrollbars=yes";

   var sz = {};
   sz.small  = "width=600,height=300";
   sz.medium = "width=700,height=530";
   sz.large  = "width=850,height=600";

   size = size ? size : "small";   // if size not specified, small

   if (! sz[size]) { 
      alert("open_win(): wrong size= " + size + " value passed");
      return false;
   }
   if (lib_debug) alert(sz[size]);
   var props = const_props + "," + sz[size];

   // Do not clutter user desktop; reuse window for same destination, i.e. url
   // Note: - win name can't have special characters, so we remove non-alphanm
   //       - we keep only alphanums, as IE would not accept the string
   var win_name   = url; 
   win_name       = win_name.replace(/[^\d\w]/g, '');
   var win_handle = window.open(url, win_name, props ); 
   win_handle.focus();  // place window on front

   return false;
}



     /*********************      SECTION 3: SEARCH     **********************/

// Sometimes we have arrays of elements that are identical, captured in
// an array on page load. We also need to attach the same event handler to them. 
// However we need to know in the handler the position of 'this' element in
// the array; rather than adding manually an "id" in each element
function lib_find_index_elem_in_array(elem, arr_elems) {

   var index = -1;
   for (var i =0; i < arr_elems.length; i++) {
      if (elem == arr_elems[i]) { index = i; break }
   }
   if (index < 0) { 
      alert("find_index_elem_in_array: element " + elem 
            + "NOT found in array " + arr_elems);       
   }
   return index;
}  // lib_find_index_elem_in_array()


/*
   we use an example from Flanagan (15-4) to hunt for elements with a
   a certain 'class'. However, rather than splitting the class string
   and check for each value as he does, we match a regexp. 
   Now: 
   - if we had to match string literal 'new_win' we could write:
     el.className.match(/\bnew_win\b/);   // \b: word boundary
   - however if 'new_win' is in param search_class, we can't do:   
     el.className.match(/\bsearch_class\b/);  // matches 'search_class'!

   When a regexp is dynamic (see Flanagan, bottom page 210), we 
   must use the Regexp constructor:
      var regex = new Regexp('\\b' + search_class + '\\b');
   [An aside: the double backslash accounts for both the string '' and
   the Regexp using the backslash for escape sequence!]
*/
function lib_find_subset_class(element_set, search_class) {
   var subset=[];
   for (var i=0; i < element_set.length; i++) {
      var el  = element_set[i];
      // dynamic regexps must use RegExp (replace & match use literals!)
      var regexp= new RegExp('\\b' + search_class + '\\b');

      // if the className is or contains this string, it's a hit
      if (    (el.className == search_class) 
           || (el.className.match(regexp))  ) {
         subset.push(el);
         /* if (lib_debug) alert("lib_find_subset_class: set " + el.className
                              + " for " + el.href)
         */
      }
   }
   return subset;
} // end lib_find_subset_class()


// this function searches for elements of a certain type (looking in each
// element's type' attribute (eg, <input type="text" ..>) in a form and 
// returns them in an array. Very useful in form validation.
function lib_get_form_elements_of_type(form, type) {
   var elems = form.elements;
   var type_elems = []; 

   for (var i=0; i < elems.length; i++) {
      // luckly, 'type' is a property of the element, so all is easy
      var el = elems[i];
      if (el.type != type) { continue } // skip elements not of type
      type_elems.push(el);
   }

   if (! type_elems) { 
     alert("lib_get_form_elements_of_type: did not find elements of type " 
            + type + " in form= " + form);
   } 
   return type_elems;
} // lib_get_form_elements_for_type


// this function searches for elements of a certain tag inside an element of
// given id (for example anchors in div)
function lib_get_tags_in_elemId(id, tag) {
   var container = lib_fetch_element(id);
   if (! container) return null;

   return container.getElementsByTagName(tag);
} //  lib_get_tags_in_elemId()



   /*******************      SECTION 4: VISUALIZATION     *****************/


// This function visualizes the child text of an element in given color
// example:         
//   html:   <span  class="hidden" id="dyn_galileo">Invisible</span> 
//   css:    .hidden { visibility: hidden }

function lib_visualize_elem_childText(elid, text, color) {

   var dyn_el = document.getElementById(elid);

   if (! dyn_el) { lib_warn_no_element(elid); return false; }

   dyn_el.firstChild.nodeValue   = text;
   dyn_el.style.visibility       = "visible";
   if (color) dyn_el.style.color = color;
   return true;
} // lib_visualize_elem_childText()


function lib_hide_elem_childText(elid) {

   var dyn_el = document.getElementById(elid);

   if (! dyn_el) { lib_warn_no_element(elid); return false; }

   // The hidden elements are usually p tags with value 'Invisible' (we had to
   // give a value for stupid IE, that otherwise would not work). 
   // If we don't reset the value, the text will be hidden but the space occupied
   // by the text will remain; not nice, because the document is not any more the
   // same as before the element is visualized. So we set it to the same value 
   // that it has by default, 'Invisible' (we had to give a stupid default value, as
   // otherwise the idiotic IE would not work).
   dyn_el.firstChild.nodeValue   = "Invisible";  

   dyn_el.style.visibility       = "hidden";
   return true;
} // lib_hide_elem_childText()


// This function visualizes an input element
// example: (css
//   html:   <input type="text" class="hidden" id="dyn_states" size="35">

function lib_visualize_input_elem(elid, text, color) {

   var dyn_el = document.getElementById(elid);

   if (! dyn_el) { lib_warn_no_element(elid); return false; }

   dyn_el.value   = text;
   dyn_el.style.visibility       = "visible";
   if (color) dyn_el.style.color = color;
   return true;
} // lib_visualize_input_elem()


/*
  Compares the Url to the navigation links to set the class for the link. 
  Javascript returns an absolute address with the attribute anchors[i].href, so
  we can compare the href against the url for equality. However, in case the
  user types an Url (against of clicking a link) without 'index.html', like
  raul/Javascript then the match will fail, and I had to fix that.
*/
function lib_setClass_ForNavigationLinkSelected(containerId, classValue) {

   // get anchors of left navigation
   var anchors  = lib_get_tags_in_elemId(containerId, "a");
   if (! anchors) return;

   var url   = document.URL;
   /* in case the user types an Url wout 'index.html', add it */
   var pattern = /[.](html|htm|cgi|pm|php)$/;  // match html, perl, php
   if (url.search(pattern) == -1) { url += "index.html"; }

   var index = -1;
   for (var i=0; i < anchors.length; i++) {
      var anchor = anchors[i];
      if (anchors[i].href == url) {
         index = i;
         if (lib_debug) alert("link selected pos= " + index);
         break;
      }
   }

   if ( index < 0) {
      alert("current url= " + url + " does not match navigation links " +
      anchors[0].href);
      return false;
   }

   // if anchors are enclosed in lists, set class of list element
   var links   = lib_get_tags_in_elemId(containerId, "li");
   var target =  (links && links.length == anchors.length) ?  links[index] 
                : anchors[index]; 
   target.className = classValue;

} // lib_setClass_ForNavigationLinkSelected()


/* This function was the first implementation of function above; it matched
   relative paths against the Url, in case that the navigation links were
   relative "dir/i.html". I did not know that Javascript returns the anchor.href 
   absolute, so this was not needed.
   Kept the function as it does interesting work:
   - extracts the last directory of each anchor and looks if the 
*/
function lib_setClassForAnchor_WithHrefInUrl(containerId, classValue) {

   // get anchors of left navigation
   var anchors  = lib_get_tags_in_elemId(containerId, "a");
   if (! anchors) return;

   // extract last directory of Url: eg: ../(Assg2)/index.html (! see note
   // above)
   var arr_match = document.URL.match(pattern);
   if (! arr_match) { 
      alert(pattern + " not found in " + document.URL);
      return null;
   }
   var lastdir = arr_match[1];
   if (lib_debug) alert(lastdir);

   // now looks if lastdir of URL is in href
   var I = -1;
   for (var i=0; i < anchors.length; i++) {
      var href = anchors[i].href;
      if (href.search(lastdir) != -1) {
         I = i; 
         if (lib_debug) alert("matched lastdir= " + lastdir + " on href= " 
                              + href + ", " + i);
         break;
      }
   }
   I = I >= 0 ? I : 0;   // if I=-1, is ../index.html or index.html, case 0
                         // in fact, the href is converted to absolute, like in
                         // href="http://raul/Course/index.html ! so, I=0 !

   anchors[I].className = classValue;

} // lib_setClassForAnchor_WithHrefInUrl()



     /************    SECTION 5:  FORM VALIDATION functions    **************/

// this function validates the value of an element against attributes pattern
// and minmax if they are present as attributes of the element
function lib_validate_element_pattern_minmax(el) {
   if (lib_debug) window.status = "; validate_element: " + el.name;
   var pattern, min_max, klass;

   // get attributes pattern or minmax
   pattern = el.getAttribute('pattern');
   min_max = el.getAttribute('min_max');

   if (el.value == el.defaultValue) { 
      klass = "default"; 
   } 
   else {
      var pattern_ok = 1;   // if there is no pattern, it's like test ok 
      var min_max_ok = 1;   // if no min_max, it's ok

      if (pattern) {
         var regexp   = new RegExp(pattern);
         klass = regexp.test(el.value) ? "valid" : "invalid";

         pattern_ok = klass == "valid" ? 1 : 0;
         if (lib_debug) window.status += "; input is " + klass;
      }

      if (pattern_ok && min_max)  {
         var min, max;
         var ar = min_max.split(/\s*:\s*/);
         min = ar[0];
         max = ar[1];
         if (lib_debug) window.status += "; min_max=" + min_max + ", min=" 
                                  + min + ", max=" + max;
         var val= parseInt(el.value);
         //klass = el.value >= min && el.value <= max ?  "valid" : "invalid";/
         klass = !isNaN(val) && (val >=min && val <= max) ? "valid" : "invalid";
         min_max_ok = klass == "valid" ? 1 : 0;
      }

      if (pattern_ok && min_max_ok) { // all ok or no constraints (optional)
         klass = "valid"; 
      }
   }
   if (lib_debug) window.status += "; klass= " + klass;
   return klass;
}  // validate_element() 


var Form_Enc = {
   pairs:  [ ],
   regexp: /%20/g,

   alert_no_name: function(el) {
      alert("form encoder: el.name missing for element type" + el.type);
   },

   add_el: function(el) {
      if (el.name) Form_Enc.add_pair(el.name, el.value);
      else Form_Enc.alert_no_name(el);
   },

   add_pair: function(name, value){
      name  = encodeURIComponent(name).replace(Form_Enc.regexp, '+');
      value = encodeURIComponent(value).replace(Form_Enc.regexp, '+');
      pair = name + '=' + value;
      Form_Enc.pairs.push(pair);
   },

   format_form_data: function(form) {
      Form_Enc.pairs = [ ];  // clear memory last transaction
      var elems = form.elements;
      for (var i=0; i < elems.length; i++) {
         el = elems[i];

         if (el.type == "checkbox" || el.type == "radio") {
            if (el.checked)  Form_Enc.add_el(el);
         }

         else if (el.type == "text"     || el.type == "textarea" ||
                  el.type == "password" || el.type == "file")  {
            Form_Enc.add_el(el);
         }

         else if  (el.type == "select-one") { 
            value = el.options(el.selectedIndex);
            if (el.name) Form_Enc.add_pair(el.name, value);
            else Form_Enc.alert_no_name(el);  
         }

         else if  (el.type == "select-multiple") { 
            for (var j=0; j < el.options.length; j++) {
               if (el.options[j].selected) {
                  value = el.options[j].value;
                  if (el.name) Form_Enc.add_pair(el.name, value);
                  else Form_Enc.alert_no_name(el);
               }
            }
         }
      }

      return Form_Enc.pairs.join('&'); 
   } // end format_form_data()

} // end encoder enc



     /************    SECTION 6:  OWN EMAIL HIDING    **************/

function cvt_codes_to_string(codes) {
   str = '';
   for (var i=0; i < codes.length; i++) { 
      str += String.fromCharCode(codes[i]);
   }
   return str;
}

function gen_html_contact_me() {
   // address
   var vals_name = [ 114,97,117,108,112,97,114,111,108,97,114,105];
   var vals_at   = [64,103,109,97,105,108,46,99,111,109]
   var vals_addr = vals_name.concat(vals_at);
   var addr  = cvt_codes_to_string(vals_addr);  // string

   return gen_html_contact(addr);

} // gen_html_contact()


function gen_html_contact(addr) {
   // scheme
   if (!addr) { alert("address to contact unknown!"); return }

   var vals_scheme = [109,97,105,108,116,111,58];
   var scheme      = cvt_codes_to_string(vals_scheme);  // string

   var dblq = '"';

   var str = '<a href=' + dblq + scheme + addr + dblq + '>' + addr + '</a>';

   //alert(str);
   return str;

} // gen_html_contact_me

/*
      vals = [ 114,97,117,108,112,97,114,111,108,97,114,105,64,103,109,97,105,108,46,99,111,109]
      s2 = ''; 
      for (i=0; i < vals.length; i++) { s2 += ( String.fromCharCode(vals[i]) ); };
      s2 =  '<a href="mailto: ' + s2 + '">contact</a>';
      alert(s2);
      document.write(s2 + '<br>');
*/
/*************************************************************************/
               // SECTION END:  OLD but interesting functions:

function OLD_lib_attach_openwinHandler_to_links_with_class(klass) {

   var links;
   links = document.getElementsByTagName("a");
   links = lib_find_subset_class(links, klass);
   for (var i=0; i < links.length; i++) {

      var link = links[i];
      var url  = link.href;
      if (lib_debug) alert("setting newWindow for link " + url);
      var size = link.className.match(/small/)  ? "small" : 
                 link.className.match(/large/)  ? "large" : "medium";
      // we could write the handler computing the url (this.href), and size
      // (this.className.matc ...) but as we already did the computation, 
      // use a closures to make the handler remember the conttext
      link.onclick = function() {
         var link = this;
         var size = link.className.match(/small/)  ? "small" : 
                    link.className.match(/large/)  ? "large" : "medium";
         lib_open_win(link.href, size); 
      }

      make_openwin_handler_in_context(url, size); 
   } 
} // OLD_lib_attach_openwinHandler_to_links_with_class() 


function OLD_make_openwin_handler_in_context(url, size) {
   // closure to capture context
   return function() { 
      lib_open_win(url, size);
      return false;
   }
} // end OLD_lib_attach_onclick_handlers_to_links_with_class()

// this produced this errror: "property has only getter, can't be set"!
// I wanted to set 'styles' rather than className; to be studied another time
function OLD_lib_setStyle_ForAnchor_WithHrefInUrl(containerId, cl_prop, cl_val) {

   // get anchors of left navigation
   var anchors  = lib_get_tags_in_elemId(containerId, "a");
   if (! anchors) return;

   // extract last directory of Url: eg: ../(Assg2)/index.html, Course/index.html
   var pattern   = /\/([\w\d]+)\/[\w\d+]+\.html$/;  

   var arr_match = document.URL.match(pattern);
   if (! arr_match) { 
      alert(pattern + " not found in " + document.URL);
      return null;
   }
   var lastdir = arr_match[1];
   if (lib_debug) alert(lastdir);

   // now looks if lastdir of URL is in href
   var I = -1;
   for (var i=0; i < anchors.length; i++) {
      var href = anchors[i].href;
      if (href.search(lastdir) != -1) {
         I = i; 
         if (lib_debug) alert("matched lastdir= " + lastdir + " on href= " 
                              + href + ", " + i);
         break;
      }
   }
   I = I >= 0 ? I : 0;   // if I=-1, is ../index.html or index.html, case 0
                         // in fact, the href is converted to absolute, like in
                         // href="http://raul/Course/index.html ! so, I=0 !

   anchors[I].style = '"' + cl_prop + '=' + cl_val ;

} // lib_setClassForAnchor_WithHrefInUrl()

               // END OF SECTION: OLD but interesting functions:
/*************************************************************************/
