/*** Drilldown Addon Javascript Controller for version 1.4x ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This version binds the http object to the drilldown forms. STILL NEED TO ATTACH UPDATE COMMAND TO THE THINGY AND TEST THIS ***/ var DEBUG_OPTION = 1; var addon_CDdrilldown_errors = ""; // GET INFORMATION FROM THE SERVER // // ** This feature puts line numbers for error-reporting out of sync var addon_CDdrilldown_base = 'http://www.turkishrent.com'; // GENERATE A NEW HTTP OBJECT // function addon_CDdrilldown_getHTTPObject() { if( typeof XMLHttpRequest != 'undefined' ){ // W3C-compliant return new XMLHttpRequest(); } // IE versions try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {} } return false; } // THOUSANDS SEPARATOR // function addon_CDdrilldown_thousands( val, separator) { var regExpRule = new RegExp('(-?[0-9]+)([0-9]{3})'); sVal = val + ''; if(separator === undefined) { separator = ","; } while(regExpRule.test(sVal)) { sVal = sVal.replace(regExpRule, '$1'+separator+'$2'); } return sVal; } // CLEAN UP SELECT TAGS // function addon_CDdrilldown_uninit(){ // This should remove the features we added to the selects // and re-enable any disabled tags, because they were being // ignored. Shock horror. var form = this; var selects = form.getElementsByTagName( 'select'); for( x in selects ){ var thisSelect = selects[x]; if( typeof thisSelect != 'object'){ continue; } if( typeof thisSelect['ddForm'] != 'undefined' ){ delete thisSelect['ddForm']; } if( typeof thisSelect['ddSpan'] != 'undefined' ){ delete thisSelect['ddSpan']; } if( typeof thisSelect['ddImg'] != 'undefined' ){ delete thisSelect['ddImg']; } if( typeof thisSelect['ddLock'] != 'undefined' ){ delete thisSelect['ddLock']; } if( typeof thisSelect['ddUnlock'] != 'undefined' ){ delete thisSelect['ddUnlock']; } if( thisSelect.disabled ){ thisSelect.disabled = false; } } return true; } // LOCK THE FIELD // function addon_CDdrilldown_lock( noUpdate ){ var thisSelect = this; var thisSpan = this.ddSpan; var pattern = /^(.*?)(?: \(\d+\))?$/; // Captures text minus any "(42)"-esque number, if such a number is present. If not, text only returned. var matches = thisSelect.options[thisSelect.selectedIndex].text.match( pattern ); if( matches ){ // Regex worked thisSpan.innerHTML = matches[1]; } else { // Regex somehow failed // Just shows the whole option text with no modifications thisSpan.innerHTML = thisSelect.options[thisSelect.selectedIndex-1].text; } thisSelect.disabled = true; thisSelect.style.display = "none"; thisSpan.style.display = "inline-block"; if( noUpdate !== true ){ // Has to check exact truth, as sometimes first param is an event thisSelect.ddForm.ddUpdate(); } return true; } // UNLOCK THE FIELD // function addon_CDdrilldown_unlock( noUpdate ){ var thisSelect; var thisSpan; switch( this.tagName ){ case 'SPAN': // Executed by the field's span element thisSpan = this; thisSelect = this.mySelect; break; case 'SELECT': // Executed by the field's select element thisSpan = this.ddSpan; thisSelect = this; break; default: // Anything else return false; // For now break; } thisSelect.disabled = false; thisSelect.style.display = "inline-block"; thisSpan.style.display = "none"; if( noUpdate !== true ){ thisSelect.ddForm.ddUpdate(); } // Make sure it selects line 1 thisSelect.selectedIndex = 0; return true; } // UPDATE A FORM // function addon_CDdrilldown_update(){ var form = this; var selects = form.getElementsByTagName('select'); var inputs = form.getElementsByTagName('input'); var used = Array(); var send = Array(); var queryStr = ""; // The field1=____&field2=_____ etc // Build the query // // *************** // // Selects first // // ************* // for(x in selects){ var thisSelect = selects[x]; if( typeof thisSelect != 'object'){continue;} // Non-obj, skip // Locked? if( thisSelect.disabled == true ){ // Locked, so SEND send.push( thisSelect.name ); } else { // Unlocked, so USED // Skip noScripts for used, but allow for send if( /\bdd-NoScript\b/.test( thisSelect.className ) ){continue;} used.push( thisSelect.name ); thisSelect.ddImg.style.visibility = "visible"; // Disable it so it's picked up for changes thisSelect.disabled = true; // Add a feature for the dd-Skip-# class if( thisSelect.className ){ var result = thisSelect.className.match( /\bdd-Steps-(\d+)\b/ ); if( result ){ queryStr += "&" + thisSelect.name + "-steps=" + result[1]; } } // Skip to the next field continue; // Skip now } // For locked fields, put their data in the query string queryStr += "&" + thisSelect.name + "=" + thisSelect.options[thisSelect.selectedIndex].value; } // End loop selects // Now the inputs // // ************** // var inputsDone = Array(); for( x in inputs ){ var thisInput = inputs[x]; if( typeof thisInput != 'object'){continue;} // Non-obj, skip //if( /\bdd-NoScript\b/.test( thisInput.className ) ){ continue; } // Noscript, skip if( !/\[\d*?\]$/.test( thisInput.name ) ) { continue; } // Skip if not an array input if (!/(checkbox)|(radio)/.test(thisInput.type)){ continue; } // Skip if not a checkbox or radio if( thisInput.checked == true ){ // Add its value if ( !(thisInput.name in inputsDone) ){ // Not already seen, so add to send. This avoids multiple // copies of the same field in send. send.push( thisInput.name.replace(/\[\d*?\]$/, "" )); } queryStr += "&" + thisInput.name + "=" + encodeURI(thisInput.value); inputsDone[thisInput.name] = true; } } // End loop inputs // Send the query // // ************** // var nocache = new Date().getTime(); queryStr = addon_CDdrilldown_base + "/addons/CDdrilldown/query.php?used=" + used.join(",") + "&send=" + send.join(",") + queryStr + "&nocache=" + nocache; form.http.open( "get", queryStr ); form.http.onreadystatechange = addon_CDdrilldown_received; // Reference to Receiver function form.http.send( null ); } // RECEIVE RESULTS BACK // function addon_CDdrilldown_received(){ var http = this; var form = this.ddForm; var selects = form.getElementsByTagName('select'); if( http.readyState == 4 ){ // READ THE DATA var fieldName; // Current Field Name var fieldData; // Current field data var thisSelect; // Select pointed to by the field name var dataBuffer = Array(); // Array of strings to be optioned and added to the field var response = http.responseText; var lines = Array(); // Array for each line of data received lines = response.split('
'); readEachLine: // <-- Label for (x in lines){ // READ EACH LINE one by one var curLine = lines[x]; // END IT, FOR WEBHOSTS WHO APPEND PAGE RENDER TIMES (etc) TO THE OUTPUT // // OR HANDLE A NEW FIELD (procedure for both is similar) if( curLine == "~QUERYEND~" || curLine.substr(0,7) == "~FIELD:" ){ // Is there a previous field? In which case, sort and apply it before quitting totally. if( dataBuffer.length > 0 ){ // Reference the field's select tag for (y in selects){ if( typeof selects[y] != 'object' ){ continue; } // Non-obj, so skip if( selects[y].name == fieldName ){ // Found thisSelect = selects[y]; break; //<-- Break out of this for } } if( /\bdd-SortAsNumber\b/.test( thisSelect.className ) ){ // Sort as a number dataBuffer.sort( addon_CDdrilldown_numberSort ); } else { // Is it a price-min/-max? if( !/.*(-min|-max)/.test( fieldName ) ){ // No, so sort it alphabetically dataBuffer.sort(); } } // Parse the buffer and insert its contents for (y in dataBuffer) { if( typeof( dataBuffer[y] ) != 'string' ){ continue; } // Skip if not a string. var matches = dataBuffer[y].match( /(.*):(.*)/i ); var optionText = matches[1]; var optionValue = matches[2]; var newOption = new Option( optionText, optionValue ); thisSelect.options.add( newOption ); } // Clear dataBuffer dataBuffer = Array(); // HIDE THE LOADING IMAGE thisSelect.ddImg.style.visibility = "hidden"; } // <-- End applying final field if( curLine == "~QUERYEND~" ){ // Break the whole update process once we've finished with Queryend. break; } // From here, we put procedure for accessing our newly discovered field fieldName = curLine.match(/^\~FIELD\: (.*)$/)[1]; // Gets those field names fine // Get the field's select tag for (y in selects){ if( typeof selects[y] != 'object' ){ continue; } // Non-obj, so skip if( selects[y].name == fieldName ){ // Found thisSelect = selects[y]; break; //<-- Break out of this for } } // NOSCRIPT /** When we encounter dd-NoScript, we keep looking and skipping ahead until we find the next field. **/ if( /\bdd-NoScript\b/.test( thisSelect.className ) ){ xSpecial = parseInt( x ) + 1; // The x incrementor is a string, not an int, so we need to recast before maths while( lines[xSpecial].substr(0,7) != "~FIELD:" ){ // Check if reached end if( lines[xSpecial] == "~QUERYEND~" ){ // End the whole update process break readEachLine; } else { // Keep advancing until you find one xSpecial++; } } // Assert X as new line x = String( xSpecial ); continue; // <-- May need to reduce x by 1 to account for natural incrementation via for{} } // EMPTY IT thisSelect.innerHTML = ""; // ADD 'ALL' OPTION var newOption = new Option( "All", "" ); thisSelect.options.add( newOption ); thisSelect.disabled = ""; } // <-- QUERYEND and FIELD are done else if (/\~ERROR\~/.test(curLine)){ var errorString = curLine.substr(8,curLine.length) addon_CDdrilldown_errors += "\n" + errorString; // Different debugging options available switch ( DEBUG_OPTION ){ case 1: /** Show an alert with the error code and break **/ alert( errorString ); return false; break; case 2: /** Simply store in addon_CDdrilldown_errors ready for pulling out by the error-getter function **/ break; } } // <-- ERRORS else if( curLine != "" ){ // IS DATA // var forDataBuffer; //var thisSelect; // <-- I think will already be set. May need to check; /** Make sure data is always in a consistent manner **/ if( curLine.indexOf( ":" ) == -1 ){ // Contains no colon, so add at end var goUpTo = curLine.lastIndexOf( " " ); forDataBuffer = curLine + ":" + curLine.substr(0, goUpTo); // "Pigeons (20)" becomes "Pigeons (20):Pigeons" } else { // Put it straight in forDataBuffer = curLine; } /** EXCEPTIONS FOR ANY MIN-MAX **/ switch( fieldName.substr(fieldName.length-4, fieldName.length) ){ case '-max': forDataBuffer = 'To ' + addon_CDdrilldown_thousands(curLine) + ':' + curLine; break; case '-min': forDataBuffer = 'From ' + addon_CDdrilldown_thousands(curLine) + ':' + curLine; break; } /** EXCEPTIONS FOR PCLASS **/ if( fieldName == 'pclass[]' ) { // Currently no exceptions } /** EXCEPTIONS FOR BLANK FIELDS **/ // Eg: Where 'city' returns ' (4)' because 4 have a blank reference. if(curLine.substr(0, curLine.lastIndexOf(' ')) == '' && curLine.substr(curLine.length-1, curLine.length) == ')') // This second line avoids conflicts with price lines. { continue; // They won't be displayed at all. } /** Place data in data buffer **/ dataBuffer.push(forDataBuffer); } // <-- DATA LINES } } } // INITIALISE THE FORMS // function addon_CDdrilldown_init() { var defaultPattern = /\bdd-Default-([^\s]+)/; // Pattern for default-value fields // Get all the forms var forms = document.getElementsByTagName( 'form' ); for (x in forms){ // Each form var thisForm = forms[x]; if( typeof(thisForm) != 'object' ){ continue; } // Non-obj, skip if( !/\bdd-DrillForm\b/.test( thisForm.className ) ){ continue; } // Non-DD Form // Gets the forms fine. // Add HTTP object to the form thisForm.http = addon_CDdrilldown_getHTTPObject(); thisForm.http.ddForm = thisForm; thisForm.ddUpdate = addon_CDdrilldown_update; // Now get their children, being selects first. var childSelects = thisForm.getElementsByTagName('select'); var childInputs = thisForm.getElementsByTagName('input'); for (y in childSelects ){ // *** SELECTS *** // var thisSelect = childSelects[y]; if( typeof( thisSelect ) != "object" ){ continue; } // Non-obj, skip // Gets the selects fine // Add references thisSelect.ddLock = addon_CDdrilldown_lock; thisSelect.ddUnlock = addon_CDdrilldown_unlock; thisSelect.ddForm = thisForm; // Add load image and span var thisSpan = document.createElement('span'); thisSpan.className = 'addon_CDdrilldown_changeSpan'; thisSpan.style.display = 'none'; thisSpan.mySelect = thisSelect; var thisImg = new Image(); thisImg.src = addon_CDdrilldown_base + "/addons/CDdrilldown/images/load.gif"; thisImg.className = 'addon_CDdrilldown_loadImg'; thisImg.mySelect = thisSelect; thisImg.style.visibility = 'hidden'; thisSelect.parentNode.insertBefore( thisImg, thisSelect.nextSibling ); thisSelect.parentNode.insertBefore( thisSpan, thisSelect ); // Does that fine too // Add behaviours and more refs thisSelect.ddSpan = thisSpan; thisSelect.ddImg = thisImg; thisSelect.onchange = thisSelect.ddLock; thisSpan.onclick = thisSelect.ddUnlock; // *** DEFAULTS *** // var matches = thisSelect.className.match( defaultPattern ); if( matches ){ var content = matches[1].replace("+", " "); // Put back the spaces (was a +) var newOption = new Option( content + " (0)", content ); // Create new option (incl. a "(0)" at the end because normally, newOption.selected = "selected"; // this feature is stripped off, unless field is price min/max thisSelect.options.add( newOption ); // Add this option to the select thisSelect.ddLock( true ); // Lock it, choosing not to update (true) } } // End looping selects for (y in childInputs ){ // *** INPUTS *** // var thisInput = childInputs[y]; if( typeof( thisInput ) != "object" ){ continue; } // Non-obj, skip if( /\bdd-NoScript\b/.test( thisInput.className ) ){ continue; } // Noscript, skip if( !/\[\d*?\]$/.test( thisInput.name ) ) { continue; } // Skip if not an array input if (!/(checkbox)|(radio)/.test(thisInput.type)){ continue; } // Skip if not a checkbox or radio // Attach a link to the form, etc // thisInput.ddForm = thisForm; thisInput.onclick = function() { this.ddForm.ddUpdate() }; // Puts form's function at arm's length, so // it runs with 'this' as the form, not the // input element. Oh the joys of object- // oriented programming. } thisForm.onsubmit = addon_CDdrilldown_uninit; thisForm.ddUpdate(); // Update this form the first time. } // End looping forms } // UPDATE ABSTRACTOR // function addon_CDdrilldown_abstract(){ // Puts a function at arms length so that 'this' does not get messed up var input = this; var form = input.ddForm; form.ddUpdate(); } // NUMERIC SORTING // function addon_CDdrilldown_numberSort( a, b ) { // A and B will be strings that need converting. /** Their formats can be: 1:Home (450) // Home property class, id 1 Some text (25) // A text option 24 (12) // A normal number 1,400 (14) // Possibly comma-delimited, but do not allow for this for now We need to retrieve a number and sort accordingly. Don't check it, just retrieve, type-cast, and let javascript typecast anything it doesn't understand to (int)0. It will also not allow float numbers. **/ // Get the values var pattern = /^(\d+)/; var aMatch = String( a ).match( pattern ); var bMatch = String( b ).match( pattern ); if( !aMatch || !bMatch ){ return 0; // Require no change } else { aMatch = parseInt( aMatch[0] ); bMatch = parseInt( bMatch[0] ); return aMatch - bMatch; } }