﻿// Copyright 2007 by Developer Utility
// Please see http://www.developerutility.com/Terms.htm for terms of use.

/*
==========================================
Grid Marshall Filter Quick list definition
==========================================
- Highlight                : highlight the filter box mouse over/out close icon
- ShowFilterGrid           : display the filter box in the browser
- CloseFilterGrid          : hide the filter box in the browser
- UpdateFilterGrid         : update the match case filter box condition and value
- ApplyFilterGrid          : update the grid data with all case matching the filter box
- RefreshGrid              : select/sort/display per page all case matching the filter box
- SelectCaseGrid           : demo purpose only, replace the normal server side SQL query with XSL query
- IsXpathString            : demo purpose only, determine the field type for XSL query
*/

/////////////////////////////////////////////////////////////////////////////////
//     THE FUNCTIONS BELOW ARE FOR DISPLAYING AND HIDDING THE FILTER BOX       //
/////////////////////////////////////////////////////////////////////////////////
function HighlightIcon(oEle) {
  try {
    if(oEle.src.indexOf("Hot") > -1) {
	    oEle.src = oEle.src.replace("Hot", "");
    } else {
        oEle.src = oEle.src.replace(".gif", "Hot.gif");
      }
		
  } catch(e) {
      StatusBarWrite("Error (fnc : " + "Highlight" + "). " + e.message, 3000);
    }
}

function ShowFilterGrid(evt, currentGridID) {
  try {
    // This is where you show the filter box of the current grid.
    // This function is call every time the "filter icon" is trigger.
    // The event (evt) is pass only as reference in case you want to tailor some special coding.
    //
    // Fields are build automatically by the Grid using the xml data head. Condition and value will 
    // change dynamically depending on their type. See function UpdateFilterGrid (below) for more detail.
    //
    // Tip:
    // Since you can have multiple filter object in the browser (one for each grid), you will always 
    // have to pass the grid object ID inside the button (or icon) calling this function.
    // An easy way of doing this is to have a generic xml template of the your "toolbar"
    // and swap "on the fly" the property before rendering on the user screen.
    
    // Call the save data entry function
    var bReturn = SaveDataEntry(evt);
    
    // Get the current grid object
		var currentGrid = arrGridMarshall[currentGridID];
    currentGrid.showFilter();
    
  } catch(e) {
      StatusBarWrite("Error (fnc : " + "ShowFilterGrid" + "). " + e.message, 3000);
    }
}

function CloseFilterGrid(evt, currentGridID) {
  try {
    // This is where you hide the filter box of the current grid.
    // This function is call every time the "filter close icon" is trigger.
    // The event (evt) is pass only as reference in case you want to tailor some special coding.
    //
    // Tip:
    // This function will attach itself automatically on the "filter close icon". You cannot
    // rename this function.
    
    // Get the current grid object
		var currentGrid = arrGridMarshall[currentGridID];
    currentGrid.hideFilter();
		
  } catch(e) {
      StatusBarWrite("Error (fnc : " + "CloseFilterGrid" + "). " + e.message, 3000);
    }
}

function UpdateFilterGrid(evt, oSelect, currentGridID) {
  try {
    // This function is call every time the field (column) of the drop down menu (html marker select) 
    // is change inside your match case filter box.
    // Condition and value will change dynamically depending on their type (base on your xml data head).
    // See the template filter (Demo1.htm/Demo2.htm) for more detail.
    // The event (evt) is pass only as reference in case you want to tailor some special coding.
    //
    // Tip:
    // This function will attach itself automatically on the close filter template icon. You cannot
    // rename this function.
    
    // Get the current grid object
		var currentGrid = arrGridMarshall[currentGridID];
    currentGrid.updateFilter(oSelect);
    
  } catch(e) {
      StatusBarWrite("Error (fnc : " + "UpdateFilterGrid" + "). " + e.message, 3000);
    }
}

/////////////////////////////////////////////////////////////////////////////////
//        THE FUNCTIONS BELOW ARE FOR APPLYING THE FILTER IN THE GRID          //
/////////////////////////////////////////////////////////////////////////////////
function ApplyFilterGrid(evt, currentGridID) {
  try {
    // This function is call every time the "apply filter icon" is trigger.
    // This is where you will send the user "custom filter" transport to the server to be execute.
    // You can also use this function to "pre-validate" sql script injection.
    // The event (evt) is pass only as reference in case you want to tailor some special coding.
    //
    // Tip:
    // This function will attach itself automatically on the apply filter template icon. You cannot
    // rename this function.
    //
    // You can also build you own filter by calling the "currentGrid.getFilter()" or 
    // "currentGrid.getFilterXml()" method or simply modify the "auto-build" filter transport 
    // i.e. "currentGrid.getFilterRequest()".
    
    // Get the current grid object
    var currentGrid = arrGridMarshall[currentGridID];
    currentGrid.hideFilter();
    
    // Refresh the grid data (and paging if necessary)
    var pageSelect = 1        // always "1" when grid refresh
    var refreshDirection = 2; // always "2" when grid refresh
    
    RefreshGrid(currentGrid, pageSelect, refreshDirection);
    
  } catch(e) {
      StatusBarWrite("Error (fnc : " + "ApplyFilterGrid" + "). " + e.message, 3000);
    }
}

function RefreshGrid(currentGrid, pageSelect, refreshDirection) {

    // Send the xml filter on the server to get back the xml body response
    var xmlFilter = currentGrid.getFilterRequest(pageSelect, rowPerPage);
	  
    // Load the new data from the server
    // In real case scenario, you will have to send a transport to the server. Since 
    // this is a demo running on local host via a xml file, we will do xPath query 
    // to retrieve the user selected data via the SelectCaseGrid function. 
    // In the transport reponse, you will have to return the total number of rows in 
    // the database table (node TableLength of the xsl return in this demo) for paging.
    var xmlSvrRsp = SelectCaseGrid(xmlFilter);
    
    // Replace the grid node body by the new one
    var xmlGrid = currentGrid.xmlDoc;
    var oNodeBody = xmlGrid.selectSingleNode(xPathQueryBody);
    var oNewNodeBody = xmlSvrRsp.selectSingleNode(xPathQueryBody).cloneNode(true);
    
    var oParent = oNodeBody.parentNode;
    oParent.replaceChild(oNewNodeBody, oNodeBody);
    
    // Refresh the grid table with the new data
    var oTable = BuildGrid(xmlGrid);
    
    // Display the grid table on user screen
    var gridShow = document.getElementById("gridShow1");
    gridShow.innerHTML = "";
    (browser.isIE) ? gridShow.innerHTML = oTable.outerHTML : gridShow.appendChild(oTable);
    
    // Refresh page X of Y if user is on first/last page display and have click the previous/next page icon
    if(refreshDirection > 0) {
      var oNode = xmlSvrRsp.selectSingleNode("Dataset/TableLength");
      var tableLength = (browser.isIE) ? oNode.text : oNode.textContent;
      
      // Allow quicker navigation with this method (google style)
      // If previous page select at the begining of the page display
      if(refreshDirection == 1) {
        pageSelect = pageSelect - maxPage + 1;
      }
      
      RefreshPageXofY(currentGrid.id, pageSelect, tableLength, refreshDirection);
    }
    
    // Demo 1 only - Reset all child-table dependant of parent (no record is currently select)
    if(currentGrid.id == "SQLServerName.CandidatesTable") {
      var childID = "SQLServerName.CandidatesAddressTable";
      var xmlGrid = arrGridMarshall[childID].xmlDoc;
      
      var oNodeBody = xmlGrid.selectSingleNode(xPathQueryBody);
      var oNewNodeBody = xmlGrid.createElement("Body");
      
      var oParent = oNodeBody.parentNode;
      oParent.replaceChild(oNewNodeBody, oNodeBody);
      
      // Build the address grid object
      var oTable = BuildGrid(xmlGrid);
      var gridShow = document.getElementById("gridShow2");
      gridShow.innerHTML = "";
      (browser.isIE) ? gridShow.innerHTML = oTable.outerHTML : gridShow.appendChild(oTable);
    }
}

function SelectCaseGrid(xmlFilter) {

  // This part is normally on the server side in PHP, ASP(.net), JSP, Java, Perl, JScript, etc. 
  // For demo purpose we are building a xPathQuery and calling a local (with no security) xml/xsl 
  // crossing file to apply filter and sorting option.
  //
  // For real case scenario, you will have to build a SQL query on the server side. Key field column(s) 
  // will always be return (and identify i.e. key="1") in the "Columns" node of the "auto-build" transport.
  //
  // Here is a demo of the "auto-build" server transport provide by the Grid Marshall object:
  //
  //  <?xml version="1.0" encoding="UTF-8"?>
  //  <Dataset id="SQLServerName.CandidatesTable" pageselect="1" rowperpage="6">
  //    <Matchs>
  //      <Match junction=""    field="CandidateID" type="int"    condition="greaterthan" value="10"/>
  //      <Match junction="and" field="LastName"    type="string" condition="beginwith"   value="Paul"/>
  //      <Match junction="or"  field="Gender"      type="select" condition="equal"       value="M"/>
  //    </Matchs>
  //    <Sorts>
  //      <Sort field="LastName" type="string" order="descending"/>
  //      <Sort field="Account"  type="float"  order="ascending"/>
  //    </Sorts>
  //    <Columns>
  //      <Column field="CandidateID" type="int"       key="1"/>
  //      <Column field="LastName"    type="string"/>
  //      <Column field="FirstName"   type="stringstrict"/>
  //      <Column field="Gender"      type="select"/>
  //      <Column field="Account"     type="float"/>
  //    </Columns>
  //  </Dataset>
  //
  // For good examples on how to build fast SQL query for paging recordsets in MySQL or SQL Server
  // please take a look at those demo:
  // Ex: SQL 2005
  // USE Test
  // SELECT *
  // FROM   (SELECT TOP 20 ROW_NUMBER() OVER (ORDER BY CandidateID ASC)
  //         AS Row, CandidateID, FirstName, LastName FROM Candidate)
  // AS CandidatePaging
  // WHERE  Row >= 11 AND Row <= 20
  //
  // Ex: MySQL
  // SELECT * FROM Candidate LIMIT 10, 10
  // 
  // For more detail on the SQL query. please refer to those site:
  // - http://www.davidhayden.com/blog/dave/archive/2005/12/30/2652.aspx - SQL Server 2005
  // - http://forums.asp.net/t/1082289.aspx - SQL Server 2005, SQL Server 2000, MS Access and MySQL
  // - http://weblogs.asp.net/pwilson/archive/2003/10/10/31456.aspx - SQL Server 2000 & 2005
  // - http://www.4guysfromrolla.com/webtech/062899-1.shtml - SQL Server 2000 & 2005
  // - http://www.sqlteam.com/article/server-side-paging-using-sql-server-2005 - SQL Server 2005
  // - http://vadivel.blogspot.com/2006/10/paging-in-sql-server-2000-and-2005.html - SQL Server 2000 & 2005
  
  // Load xml and map xsl (in real case scenario, open the the database connection)
  var xPathQuery = "Dataset";
  var oNode = xmlFilter.selectSingleNode(xPathQuery);
  var currentGridID = oNode.getAttribute("id");
  var xmlDoc;
  
  if(currentGridID == "SQLServerName.CandidatesTable") {
    xmlDoc = GetXmlDoc("Demo1DataCandidatesTableBody.xml");   // Demo 1
  } else {
      xmlDoc = GetXmlDoc("Demo2DataCandidatesTableBody.xml"); // Demo 2
    }
  var xslFile = "Filter.xsl";
  
  // Display only the node of the current page (paging X of Y)
  var pageselect = parseInt(oNode.getAttribute("pageselect"));
  var rowperpage = parseInt(oNode.getAttribute("rowperpage"));
  var startingNode = (pageselect - 1) * rowperpage;
  var endingNode = startingNode + rowperpage + 1;
  
  var arrPara = new Array();
  arrPara["startingNode"] = startingNode;
  arrPara["endingNode"] = endingNode;
  
  // Sort xml data via parameter in the xsl file
  xPathQuery = "Dataset/Sorts/*";
  var oNodeList = (browser.isIE) ? xmlFilter.selectNodes(xPathQuery) : xmlFilter.selectNodes(xPathQuery, 1);
  var filterLength = (browser.isIE) ? oNodeList.length : oNodeList.snapshotLength;
  
  var field;
  var type;
  var order;
  
  var i = 0;
  while(i < filterLength) {
    oNode = (browser.isIE) ? oNodeList(i) : oNodeList.snapshotItem(i);
    
    field = oNode.getAttribute("field");
    type = oNode.getAttribute("type");
    order = oNode.getAttribute("order");
    
	  switch(type) {
      case "int": case "float": 
        type = "number";
	      break;
      default: // string, stringstrict, select, checkbox, date
        type = "text";
        break;
    }
    
    arrPara["sortField" + i] = field;
    arrPara["sortType" + i] = type;
    arrPara["sortOrder" + i] = order;
    
    i++;
  }
  
  // Build xPath query with user filter option
  xPathQuery = "Dataset/Matchs/*";
  oNodeList = (browser.isIE) ? xmlFilter.selectNodes(xPathQuery) : xmlFilter.selectNodes(xPathQuery);
  oNode = (browser.isIE) ? oNodeList.nextNode : oNodeList.iterateNext();
  
  var junction;
  var condition;
  var value;
  var quote;
  var AZ = "'ABCDEFGHIJKLMNOPQRSTUVWXYZ'";
  var az = "'abcdefghijklmnopqrstuvwxyz'";
  
  xPathQuery = "Body/Candidates"; // Default xPath query (select all node)
  var xPathFilter = new Array();
  
  var i = 0;
  while(oNode) {
    field = "translate(" + oNode.getAttribute("field") + ", " + AZ + ", " + az + ")"; // Make xPath case insensitive
    type = oNode.getAttribute("type");
    junction = oNode.getAttribute("junction");
    condition = oNode.getAttribute("condition");
    quote = IsXpathString(type);
    value = quote + oNode.getAttribute("value").toLowerCase() + quote; // Make xPath case insensitive
    
	  switch(condition) {
      case "equal":
        xPathFilter.push(junction + " " + field + " = " + value);
        break;
      case "equalnot":
        xPathFilter.push(junction + " not(" + field + " = " + value + ")");
        break;
      case "greaterthan":
        xPathFilter.push(junction + " " + field + " > " + value);
        break;
      case "greaterthanorequal":
        xPathFilter.push(junction + " " + field + " >= " + value);
        break;
      case "lessthan":
        xPathFilter.push(junction + " " + field + " < " + value);
        break;
      case "lessthanorequal":
        xPathFilter.push(junction + " " + field + " <= " + value);
        break;
      case "isempty":
        xPathFilter.push(junction + " not(" + field + ")");
        break;
      case "isemptynot":
        xPathFilter.push(junction + " " + field);
        break;
      case "contain":
        xPathFilter.push(junction + " contains(" + field + ", " + value + ")");
        break;
      case "containnot":
        xPathFilter.push(junction + " not(contains(" + field + ", " + value + "))");
        break;
      case "beginwith":
        xPathFilter.push(junction + " starts-with(" + field + ", " + value + ")");
        break;
    }
    
    oNode = (browser.isIE) ? oNodeList.nextNode : oNodeList.iterateNext();
  }
  
  // Make sure there is at least one user filter option
  if(xPathFilter.length > 0) {
    xPathQuery = xPathQuery + "[" + xPathFilter.join(" ") + "]";
  }
  
  var oNodeList = xmlDoc.selectNodes(xPathQuery);
  arrPara["filter"] = oNodeList;
  
  // Call the generic crossing xml/xsl function
  var oFragment = RenderXmlXsl(xmlDoc, xslFile, arrPara);
  
  var xmlResult = (browser.isIE) ? CreateXmlDoc(oFragment) : CreateXmlDoc(oFragment.xml());
  return xmlResult;
}

function IsXpathString(type) {
  var quote;
  
  switch(type) {
    case "int": case "float": 
      quote = "";
      break;
    default: // string, stringstrict, select, checkbox, date
      quote = "'";
      break;
  }
  return quote;
}
