zip Code Validation

TimJacobs 12 Apr, 2009
Ok, here is what I am wondering..

Lets Say that we have a form (i'll try to keep it simple)

address_text
city_text maxlength = 0
state_text maxlength = 0
zip_text with drop down

I am looking for 2 parts to this

1) Zip code must be within a certain range ( 77000-79023, 81205, 59465, 58740-48772)
2) Once the Zip code is entered the City and state are auto fill in

I would have no idea where to start. So any help on this would be appreciated.

T
Max_admin 13 Apr, 2009
Hi Tim,

you can use the validation in RC4.11 to set these limits, you will need to hack it alittle though, for the auto fill, you need a zip to city database ? with AJAX, I think Bob may have a sample code for this!

Regards
Max
Max, ChronoForms developer
ChronoMyAdmin: Database administration within Joomla, no phpMyAdmin needed.
ChronoMails simplifies Joomla email: newsletters, logging, and custom templates.
TimJacobs 13 Apr, 2009
Ok, Thanks I'll be waiting to see the code if he does......
GreyHead 13 Apr, 2009
Hi TimJacobs,

I have a very similar site working OK. If you post your key form html and the database field structure for the zipcode table here (or email /PM them to me) I'll take a look in the morning.

Bob
TimJacobs 13 Apr, 2009
Thanks But so far I really don't have anything as far a code yet
GreyHead 14 Apr, 2009
Hi Tim,

I'll try and work a tutorial post on this - it may come in several chunks.

First off we need a zip code database table. I found a free one here which may not be completely accurate but will do for a demo. The easy way to deal with your range selection would be to include only the valid zipcodes in the table.

To get this into my database I need to convert it into MySQL which I did using the handy on-line converter here. (Note: I suppressed RECNO creation as I can use the Zip code as a primary index). The converter outputs a text file which renamed to .sql and uploaded to my database using MySQL Query Browser. To finish this step I edited the table and field names to make them more meaningful, set zipcode as an index and cleaned up some of the data types. (NB I made a mistake and created the zipcode column as an integer, because of this the results starting with 0 or 00 need re-formatting which I haven't done).

Next I need a simple form which I'll create in the ChronoForms Form Wizard with fields for Zipcode, City, State and a Submit button. I made one change to the Wizard html and edited the ids of the form inputs to match the names: 'zipcode', 'city' and 'state' as this makes the JavaScript simpler.

We're going to use a MooTools Ajax/JSON call to get the information from the database. The documents for the JSON Response from MooTools 1.1 (Joomla 1.5 uses this version, not the later 1.2) are here.

To make sure that MooTools is loaded set 'Enable Validation?' to Yes in the Validation tab.

Here's the core of JavaScript that I ended up with
  $('zipcode').addEvent('blur', function() {
    var zipcode_v = $('zipcode').value;
    // set the ChronoForms Ajax URL
    var url = 'index.php?option=com_chronocontact&chronoformname=test_form_10&task=extra&format=raw';
    var jSonRequest = new Json.Remote(url, {onComplete: function(zipcode){
       // set the form values on completion
      $('zipcode').value = zipcode.zipcode;
      $('city').value = zipcode.city;
      $('state').value = zipcode.state;
        // set the value to search for
    }}).send({'zipcode': zipcode_v});
  });

Most of this is straight from the MooTools documentation with the variable names and the onComplete actions changed.

The important piece to notice here is the url that we are using for the Ajax call. index.php?option=com_chronocontact&chronoformname=test_form_10 is the same as the link that we see in the Forms Manager; &task=extra tells ChronoForms that we want to execute code from one of the Extra Code boxes for our form (you can use &extraid=2 etc. to specify a box but for us the default 1 is fine) &format=raw tells Joomla to return the 'bare' results with no templating or html tags.

This code will send a request back to our form code - we need to add code in the Extra Code 1 box to respond to it:
<?php
$db =& JFactory::getDBO();
// get the input data from the form and 'decode' it
$input = JRequest::getString('json', '', 'post');
$input = json_decode($input);
// build our query
$query = "
  SELECT `zipcode`, `city`, `state`
    FROM `#__zipcodes`
    WHERE `zipcode` = ".$db->Quote($input->zipcode).";
";
$db->setQuery($query);
$data = $db->loadObject();
// if no result is found add an 'error' object
if ( !count($data) ) {
  $data->zipcode = 'Zipcode not found';
  $data->city = '-';
  $data->state = '-';
}
// encode and return the result
echo json_encode($data);
?>

This is a straightforward database query apart from the json_decode and json_encode at the beginning and end; they are used to pack and unpack the data from the form and the results. Note that the input data is in the 'json' post variable.

With a little tidying up this works well as a basic form. You type in a zipcode and when the field loses focus (the onBlur event) the zipcode is looked up and the results entered into the form fields.

[attachment=0]14-04-2009 12-26-12.png[/attachment]

This is an excellent simple demo that demonstrates the principles and shows how to use Ajax with ChronoForms RC 4.11 on; though it can be improved a lot. In practice I used Harald Kirschener's AutoCompleter to add more features.

Bob

The complete HTML code, including the JavaScript, is
<?php
$script = "
window.addEvent('domready', function() {
  $('zipcode').addEvent('blur', function() {
    var zipcode_v = $('zipcode').value;
    var url = 'index.php?option=com_chronocontact&chronoformname=test_form_10&task=extra&format=raw';
    var jSonRequest = new Json.Remote(url, {onComplete: function(zipcode){
      $('zipcode').value = zipcode.zipcode;
      $('city').value = zipcode.city;
      $('state').value = zipcode.state;
    }}).send({'zipcode': zipcode_v});
  });
});
";
$doc =& JFactory::getDocument();
$doc->addScriptDeclaration($script);
?>
<div class="form_item">
  <div class="form_element cf_textbox">
    <label class="cf_label">Zipcode</label>
    <input class="cf_inputbox" maxlength="150" size="30" title="" id="zipcode" name="zipcode" type="text" />
  </div>
  <div class="clear"> </div>
</div>

<div class="form_item">
  <div class="form_element cf_textbox">
    <label class="cf_label">City</label>
    <input class="cf_inputbox" maxlength="150" size="30" title="" id="city" name="city" type="text" />
  </div>
  <div class="clear"> </div>
</div>

<div class="form_item">
  <div class="form_element cf_textbox">
    <label class="cf_label">State</label>
    <input class="cf_inputbox" maxlength="150" size="30" title="" id="state" name="state" type="text" />
  </div>
  <div class="clear"> </div>
</div>

<div class="form_item">
  <div class="form_element cf_button">
    <input value="Submit" type="submit" />
  </div>
  <div class="clear"> </div>
</div>
TimJacobs 15 Apr, 2009
OK, I will give this a try and I will let you know how it goes....... Thanks
gemlog 04 Sep, 2009
    $input = JRequest::getString('json', '', 'post');
    $input = json_decode($input);


The server is rhel4 and php4, so I stuck in a json lib with two compat functions on the end when it failed on json_decode. This may well be the difference, but it's whinging about needing two args and only got one for json_decode:

function json_decode($data, $bool) {


It may be just a horrible kludge to get json on php4 that I've implemented, or I may have simply messed it up. I used this idea:
http://forums.mawhorter.net/viewtopic.php?id=8
but I just stuck the include at the head of extracode1 and it may be needed earlier?
GreyHead 04 Sep, 2009
Hi gemlog,

The obvious answr is to get onto a PHP5 server asap - PHP4 is well past it's sell-by date.

After that you'll need to look at the docs for your json file and see what the second parameter is - or just try 'true' and 'false' and see which one works.

Bob
gemlog 04 Sep, 2009
"the obvious answer" has been obvious for years. Circumstances prevent it...

I'll just try to muddle through. Thanks very much for your reply gh.
gemlog 05 Sep, 2009
Well gh, you'll be happy to know that my desire to use chronoforms inspired me to compile php5 to /usr/local, so that one vhost is now on php5.2.10. No packages available for rhel4 to do that, so no other option. :-)

Anyhow, I get the same error as when using my kludged up php4 -- I just get the error object back {"zipcode":"Zipcode not found","city":"-","state":"-"} The query runs fine if I just type it in at a command line, but, of course, I give it something to match :-)

Without task=extra it loads the form fine, but with task=extra I get the error. Perhaps I've messed up where I placed everything from your tutorial? Why is it running before presenting the form? My table happens to be called 'zipcode', same as the field name and I made zipcode char(5) and pk. Other than that I just copy/pasted.

Form Html:
<div class="form_item">
  <div class="form_element cf_textbox">
    <label class="cf_label" style="width: 150px;">Zipcode</label>
    <input class="cf_inputbox required validate-alphanum" maxlength="7" size="30" title="" id="text_4" name="zipcode" type="text" />
  <a class="tooltiplink" onclick="return false;"><img height="16" border="0" width="16" class="tooltipimg" alt="" src="components/com_chronocontact/css/images/tooltip.png"/></a>
				<div class="tooltipdiv">Zipcode :: Please Enter Your Zipcode</div>
  </div>
  <div class="cfclear"> </div>
</div>

<div class="form_item">
  <div class="form_element cf_textbox">
    <label class="cf_label" style="width: 150px;">City</label>
    <input class="cf_inputbox" maxlength="150" size="30" title="" id="text_2" name="city" type="text" />
  </div>
  <div class="cfclear"> </div>
</div>
<div class="form_item">
  <div class="form_element cf_textbox">
    <label class="cf_label" style="width: 150px;">State</label>
    <input class="cf_inputbox validate-alpha" maxlength="150" size="30" title="" id="text_5" name="state" type="text" />
  </div>


Form javascript:
<?php
$script = "
window.addEvent('domready', function() {
  $('zipcode').addEvent('blur', function() {
    var zipcode_v = $('zipcode').value;
    var url = 'index.php?option=com_chronocontact&chronoformname=test_form_10&task=extra&format=raw';
    var jSonRequest = new Json.Remote(url, {onComplete: function(zipcode){
      $('zipcode').value = zipcode.zipcode;
      $('city').value = zipcode.city;
      $('state').value = zipcode.state;
    }}).send({'zipcode': zipcode_v});
  });
});
";
$doc =& JFactory::getDocument();
$doc->addScriptDeclaration($script);
?>


Extracode1:
    <?php
    $db =& JFactory::getDBO();
    // get the input data from the form and 'decode' it
    $input = JRequest::getString('json', '', 'post');
    $input = json_decode($input);
    // build our query
    $query = "
      SELECT `zipcode`, `city`, `state`
        FROM `#__zipcode`
        WHERE `zipcode` = ".$db->Quote($input->zipcode).";
    ";
    $db->setQuery($query);
    $data = $db->loadObject();
    // if no result is found add an 'error' object
    if ( !count($data) ) {
      $data->zipcode = 'Zipcode not found';
      $data->city = '-';
      $data->state = '-';
    }
    // encode and return the result
    echo json_encode($data);
    ?>
GreyHead 05 Sep, 2009
Hi gemlog,

I know nothing about Linux - but it seems to me that the PHP5 install may be possible - see here

It's past midnight now here - and json makes my eyes close. I'll take a better look in the morning.

Bob
gemlog 06 Sep, 2009
Thanks for replying so late at night. I had seen that page a couple of days ago when I was looking for an "easy way out", but that plan involves replacing php4, which I can't do at the present time. I compiled php5 and am using it at the same time as php4, it's just that the one vhost with chronoforms in it is running php-cgi from php5.

I hard-wired the select and get back the correct answer, I just never see the form as the query gets executed before it even loads. i.e. changed
          WHERE `zipcode` = 90210;";
//        WHERE `zipcode` = ".$db->Quote($input->zipcode).";";
    $db->setQuery($query);


And it comes back correctly with: {"zipcode":"90210","city":"Beverly Hills","state":"CA"} but still no form unless I take the task off the url.

I very well should recompile it to add more config options, but I was getting the same error from my work-around in php4 with the added json lib too. I think I've prolly just missed something basic about chronoforms. Cheers.
gemlog 08 Sep, 2009
Does someone have a cp of this working somewhere that I could look at to see the difference in the page?
edit 2009-09-10: I do! yay! :-)
Between a misunderstanding of how chronoforms were *supposed* to work and a doubled up stanza in my script...
Thanks for your patience with me gh.
gemlog 28 Oct, 2009
I've had some good fun now with chronoforms -- it's very nice when things are separated like that actually. The edit windows are a bit small is all, so I cp/paste from my text editor. After I got your zipcode thing working things went a whole lot easier with chronoforms after I finally understood a few things about it! Prolly should have picked something easier for my first one :-)

I've been trying to make your tip about the autocomplete work for my city field, but no luck using the 1.0 version from his website. Do you still have what you were playing with to cp/paste here Bob? Or did you replace mootools with the newest one and use his 1.1 version of autocompleter?

Here is a url. The example postal code is valid and any other one will look up the mp (v8g1n8, l6z4n5) on key up. The script has to go and scrape the govt site at that point. Step 2 has some basic fields with the province set from the postal code. I would like to have the city autocomplete from a table I made of .ca cities and towns.

http://gemlog.ca/index.php?option=com_chronocontact&chronoformname=pcode_22

I'm ok with sql, php etc. so just any working code with no changes would be great. No need to tutorialize it for me at all.

(I subscribed after zipcode worked, but I didn't register my dev site with chrono, so that's why the url is still down there.)
GreyHead 28 Oct, 2009
Hi gemlog,

I use the 1.0 version of AutoComplete with the standard Joomla MooTools 1.11

Here is the onload() function from my current development version of the Joomla Registration plugin. There are AJAX checks on the username and email fields. This includes both the script to call the Autocompleter and the function that returns the Json reply.
	function onload( $option, $row, $params, $html_string )
	{
		global $mainframe;

		if ( $row->form_id ) {
			$formname = CFChronoForm::getFormName($row->form_id);
		} else {
			$formname = JRequest::getVar('chronoformname');
		}
		$scripts = $styles = array();
        $MyForm =& CFChronoForm::getInstance($formname);

        if ( $params->get('ajaxverify') ) {
            
            $scripts[] = JPATH_SITE.DS.'components'.DS.'com_chronocontact'.DS.'assets'.DS.'ajax'.DS.'Autocompleter.js';
            $scripts[] = JPATH_SITE.DS.'components'.DS.'com_chronocontact'.DS.'assets'.DS.'ajax'.DS.'Observer.js';
            $styles[]  = JPATH_SITE.DS.'components'.DS.'com_chronocontact'.DS.'assets'.DS.'ajax'.DS.'Autocompleter.css';
            // check if this is an AJAX verify call
    		$task = JRequest::getString('task', '', 'get');
    		if ( $task == 'verify' ) {
    		    JHTML::_('behavior.mootools');
    		    //echo  "Task is $task";
    		    //$mainframe->enqueuemessage("Task is $task");
    		    $json = stripslashes($_POST['json']);
                $json = json_decode($json);

                if ( isset($json->username) ) {
                    $username = strtolower(trim($json->username));
                    $db =& JFactory::getDBO();
                    $query = "
                        SELECT COUNT(*)
                        	FROM `#__users`
                        	WHERE LOWER(`username`) = ".$db->quote($username).";";
                    $db->setQuery($query);
                    $response = (bool) !$db->loadResult();
                    $response = array('username_ok' => $response );
                    echo json_encode($response);
                } elseif ( isset($json->email) ) {
                    $email = strtolower(trim($json->email));
                    $db =& JFactory::getDBO();
                    $query = "
                        SELECT COUNT(*)
                        	FROM `#__users`
                        	WHERE LOWER(`email`) = ".$db->quote($email).";";
                    $db->setQuery($query);
                    $response = (bool) !$db->loadResult();
                    $response = array('email_ok' => $response );
                    echo json_encode($response);
                }
    		    $MyForm->stopRunning = true;
    		    die;
    		}
            $script  = "";
            if ( $params->get('username') ) {
                $script .= "
                var username = $('ChronoContact_".$formname."').getElement('input[name=".$params->get('username')."]');
                var url = 'index.php?option=com_chronocontact&chronoformname=".$formname."&task=verify&format=raw';
                username.addEvent('blur', function() {
                	username.setStyle('background-color', 'white');
                    regex = /<|>|\"|'|%|;|\(|\)|&/i;
                	var value = username.value.trim();
                	if ( value.length > 0 && !regex.test(value) ) {
                    	var jSonRequest = new Json.Remote(url, {
                        	onComplete: function(r) {
                        		if ( r.username_ok ) {
                        			username.setStyle('background-color', 'green');
                        		} else {
                        			username.setStyle('background-color', 'red');
                        		}
                        	}
                        }).send({'username': username.value});
                    }
                });
                ";
            }
            if ( $params->get('email') ) {
                $script .= "
                var email = $('ChronoContact_".$formname."').getElement('input[name=".$params->get('email')."]');
                email.addEvent('blur', function() {
                	email.setStyle('background-color', 'white');
                	regex = /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i;
                	var value = email.value.trim();
                	if ( value.length > 6 && regex.test(value) ) {

                    	var jSonRequest = new Json.Remote(url, {
                        	onComplete: function(r) {
                        		if ( r.email_ok ) {
                        			email.setStyle('background-color', 'green');
                        		} else {
                        			email.setStyle('background-color', 'red');
                        		}
                        	}
                        }).send({'email': email.value});
                    }
                });
                ";
            }
        }
        $doc =& JFactory::getDocument();
        if ( $script ) {
            $script = "window.addEvent('domready', function() { $script }); ";
            $doc->addScriptDeclaration($script);
        }
        if ( count($scripts )) {
            foreach ($scripts as $script ) {
                $doc->addScript($script);
            }
        }
	    if ( count($styles ) ) {
            foreach ($styles as $style ) {
                $doc->addStyleSheet($style);
            }
        }
		$user = JFactory::getUser();
		if ( ($user->id) && ($mainframe->isSite()) ) {
			$html_string = 'You can not re-register while you are already signed in';
		}
		return $html_string ;
	}
Note, You need the digiharald files in the components/com_chronocontact/assets/ajax/ folder; and this version has not been tested so may need debugging!

Bob

PS I don't like the small edit windows either so I put a code snippet in there and use a FTP editor to workon the code
<?php
require_once(JPATH_SITE.DS.'components'.DS.'com_chronocontact'.DS.'includes'.DS.'test_form_99'.DS.'formhtml.php');
?>
This has some small disadvantages as ChronoForms can't 'see' the code but works vey well for me.
GreyHead 28 Oct, 2009
Hi gemlog,

After posting this I see it isn't using the AutoCompleter and I added some uneccessary code. I'll post a better example later.

Bob
gemlog 28 Oct, 2009
I learned some new joomla vars from your post anyhow :-)
GreyHead 28 Oct, 2009
Hi gemlog,

Trying again . . .

Extracted from the Form HTML
<?php
defined('_JEXEC') or die('Restricted access');

$doc     =& JFactory::getDocument();
$db      =& JFactory::getDBO();
$user    =& JFactory::getUser();

// load styles
$script = $style = "";
$script_array = $style_array = array();

$style .= "
img.autocompleter-loading {
	vertical-align:		top;
}
";
$style_array[] = JURI::base().'components/com_chronocontact/includes/assets/Autocompleter.css';
$script_array[] = JURI::base().'components/com_chronocontact/includes/oproep_reg/javascript.js';
$script_array[] = JURI::base().'components/com_chronocontact/includes/assets/Observer.js';
$script_array[] = JURI::base().'components/com_chronocontact/includes/assets/Autocompleter.js';

<!--  new plaats selector -->
<div class="form_item">
  <div class="form_element cf_textbox">
    <label class="cf_label" style="width: 150px;">In deze plaats</label>
    <input class="cf_inputbox" maxlength="150" size="40" id="plaats_naam"
        name="plaats_naam" type="text" value='<?php echo $plaats_naam; ?>' /><label></label>
  </div>
  <div class="clear"> </div>
</div>
<!--  new plaats selector -->
<?php
// load scripts and styles
if ( $script ) {
    $script = "window.addEvent('domready', function() { $script });";
    $doc->addScriptDeclaration($script);
}
if ( count($script_array) ) {
    foreach ($script_array as $s) {
        $doc->addScript($s);
    }
}
if ( $style ) {
    $doc->addStyleDeclaration($style);
}
if ( count($style_array) ) {
    foreach ($style_array as $s) {
        $doc->addStylesheet($s);
    }
}
?>
From the javascript.js file that I included
window.addEvent('domready', function() { 
	// Ajax request
	var inputWord = $('plaats_naam'); 
	
	// A simple spinner div, display-toggled during request
	var indicator = new Element('div', {
	    'class': 'autocompleter-loading',
	    'styles': {'display': 'none'}
	}).injectAfter(inputWord); // appended after the input

	 
	// Our instance for the element with id "postcode"
	var url = "index.php?option=com_chronocontact&chronoformname=oproep_reg&task=extra&extraid=5&format=raw";
	new Autocompleter.Ajax.Json(inputWord, url, {
		'indicatorClass': 'autocompleter-loading',
		'markQuery': true,
		'minLength': 2,
		'onSelect': updatePlaats
	});

	function updatePlaats() {
	//inputWord.addEvent('change', function() { 
		console.log(completer);
		if ( inputWord.value ) {
			$('plaats_id').value = inputWord.key;		
		}
	};
	});
and the code from the Extra Code 5 box that is the responder
<?php
defined('_JEXEC') or die('Restricted access');

$code =& JRequest::getString('value', '', 'post');
$db =& JFactory::getDBO();
$query = "
    SELECT `plaats_name` AS 'value'
        FROM `#__gh_plaats`
        WHERE `plaats_name` LIKE '$code%'
        	AND `published` = 1 ;
    ";
$db->setQuery($query);
$data = $db->loadAssocList();

if ( !count($data) ) {
    $data[0] = array('value' => 'Niets gevonden');
}
echo json_encode($data);
?>

Bob
gemlog 28 Oct, 2009
Thanks Bob!!

Part of that looks entirely unlike what I was trying...

I wish you a good evening. Thanks very much.

edit 2009-10-29:
Thought I should pop back to confirm that worked great in case others read this thread. Other than changing var names etc. I only had to change from assoc list to $db->loadResultArray() for my simpler application. Think that's all I did other than correct my *own* typo for the table name. In the end, my problems were caused by sometimes looking at 1.0 and sometimes at 1.1 over at digitharald.de -- the pages look quite similar, and one has way more info on it: the wrong 1.1 page :-/

Thanks again Bob.
This topic is locked and no more replies can be posted.

VPS & Email Hosting 20% discount
hostinger