<?php
/**
* Copyright 2005 Zervaas Enterprises (www.zervaas.com.au)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
require_once('AjaxACApplication.class.php');
/**
* CountryRegionCityJax
*
* A sample AjaxAC application used to populate Country/Region/City dropdown
* select boxes. All data is dynamically loaded in the background. When a
* country is selected, all the regions for that country are fetched and placed
* into the region dropdown, then likewise with cities when a region is selected.
* It also deals with fancy stuff like disabling the select boxes when
* applicable, as well as putting 'loading' style text.
*
* There are several limitations at this point, such as no non-javascript
* fallback. Also, this code is bloatware, however in the near future there will
* hopefully be PHP functions to deal with generating much of this code. Another
* drawback is that the text file method we're using is fairly inefficient,
* however the whole thing could easily be hooked into a database, but the point
* of this sample is to demonstrate the framework stuff, not to deal with
* databases. Another issue is that at this point we have no standard data
* exchange format, so we're just returning JavaScript code for creating arrays
*/
class CountryRegionCityJax extends AjaxACApplication
{
// the file location data is stored in. Each city should be stored on
// a separate line, in the form: CountryName,RegionName,CityName
var $data = 'locations.txt';
function CountryRegionCityJax($config = null)
{
parent::AjaxACApplication($config);
$this->registerActions('getcountries', 'getregions', 'getcities');
// This application contains 3 actions:
// 1. action_getcountries(), for fetching a country list
// 2. action_getregions(), for fetching a region list for a given country
// 3. action_getcities(), for fetching a city list for a given country/region
// See below for the implementation of each of these methods
$this->setup();
}
function setup()
{
$this->addJsLib('formtools.js');
// there's a bunch of utility functions that never change in this file
// Next we need to create the HTML select elements. There is one
// to list countries, one to list regions and one to list cities.
// We want to perform certain actions on each when they are loaded,
// so the onload event is added to each of them, with their own
// callback (event_countryinit(), event_regioninit(), event_cityinit()).
// Additionally, when a country is changed, we need to update the
// region list, so we add the onchange element to the country.
// Likewise we add onchange to regions so the cities dropdown
// can be updated
// And finally, each widget is added to the application using addWidget()
$country = $this->createWidget('country');
$country->addEvent(AJAXAC_EV_ONLOAD, 'countryinit');
$country->addEvent(AJAXAC_EV_ONCHANGE, 'countrychange');
$this->addWidget($country);
$region = $this->createWidget('region');
$region->addEvent(AJAXAC_EV_ONLOAD, 'regioninit');
$region->addEvent(AJAXAC_EV_ONCHANGE, 'regionchange');
$this->addWidget($region);
$city = $this->createWidget('city');
$city->addEvent(AJAXAC_EV_ONLOAD, 'cityinit');
$this->addWidget($city);
// after having all done this, a number of things must now be done:
// 1. Create a HTML page (see index.php) containing 3 dropdowns,
// as well as attaching them to each of these widgets
// 2. Implement event_countryinit()
// 3. Implement event_countrychange()
// 4. Implement event_regioninit()
// 5. Implement event_regionchange()
// 6. Implement event_cityinit()
}
/**
* Handle the getcountries action, by returning a JavaScript array
* of all the countries, or an empty array if none are found
*/
function action_getcountries()
{
$countries = $this->getCountries();
$this->sendResponseData('jsarray', $countries);
}
/**
* Handle the getregions action, by returning a JavaScript array
* of all the regions for the request country, or an empty array
* if none are found
*/
function action_getregions()
{
$regions = $this->getRegions($this->getRequestValue('c'));
$this->sendResponseData('jsarray', $regions);
}
/**
* Handle the getcities action, by returning a JavaScript array
* of all the cities for the request region/country, or an empty array
* if none are found
*/
function action_getcities()
{
$cities = $this->getCities($this->getRequestValue('c'), $this->getRequestValue('r'));
$this->sendResponseData('jsarray', $cities);
}
/**
* Initialises the country dropdown selector
*/
function event_countryinit(&$widget, $event)
{
// create a new xmlhttp widget so we can fetch the initial list
// of countries. the action to do this is getcountries.
// once the country list is ready, we process this data with
// the handlecountries event callback
$xmlhttp = $this->XMLHttpRequest('r1', AJAXAC_METH_GET);
$xmlhttp->setFilenameFromString($this->getApplicationUrl('getcountries'));
$xmlhttp->addEvent(AJAXAC_EV_ONXMLHTTPSUCCESS, 'handlecountries');
// callback description:
// 1. Initialize various text values for the element such as default text
// 2. Set the 3 dropdowns to their required state while the country list is loading
// 3. Submit the HTTP subrequest to fetch the country data
$callback = "
function()
{
this.emptyDefault = '%s';
this.fullDefault = '%s';
this.loadingText = '%s';
loadingCountry(this, %s, %s);
try {
%s
}
catch (e) { }
return false;
}
";
$callback = sprintf($callback,
$this->escapeJs($this->getConfigValue('countryEmptyDefault')),
$this->escapeJs($this->getConfigValue('countryFullDefault')),
$this->escapeJs($this->getConfigValue('countryLoadingText')),
$this->getHookName('region'),
$this->getHookName('city'),
$xmlhttp->getJsCode());
return $callback;
}
/**
* Initialises the country dropdown selector. Basically just sets up text strings
* for various states
*/
function event_regioninit(&$widget, $event)
{
$callback = "
function()
{
this.emptyDefault = '%s';
this.fullDefault = '%s';
this.loadingText = '%s';
}
";
$callback = sprintf($callback,
$this->escapeJs($this->getConfigValue('regionEmptyDefault')),
$this->escapeJs($this->getConfigValue('regionFullDefault')),
$this->escapeJs($this->getConfigValue('regionLoadingText')));
return $callback;
}
/**
* Initialises the country dropdown selector. Basically just sets up text strings
* for various states. It has an extra one to regions, as there is text to indicate
* to select a country when no country has been selected, or to select a region when
* a country has been selected but no region has.
*/
function event_cityinit(&$widget, $event)
{
$callback = "
function()
{
this.emptyDefault = '%s';
this.emptyDefaultRegion = '%s';
this.fullDefault = '%s';
this.loadingText = '%s';
}
";
$callback = sprintf($callback,
$this->escapeJs($this->getConfigValue('cityEmptyNoCountryDefault')),
$this->escapeJs($this->getConfigValue('cityEmptyNoRegionDefault')),
$this->escapeJs($this->getConfigValue('cityFullDefault')),
$this->escapeJs($this->getConfigValue('cityLoadingText')));
return $callback;
}
/**
* Deals with the returned country data once the XMLHttp request is completed
*/
function event_handlecountries(&$widget, $event)
{
// callback description:
// 1. Firstly checks the the HTTP subrequest is complete
// 2. Next checks that it was a valid 200 response header
// 3. Assigns the returned JavaScript array from action_getcountries() to _data
// 4. Populates the country dropdown with what is in _data
// 5. Set the 3 dropdowns to their required state for country data just being loaded
// (this is basically, tell user to pick a country, and disable region/city until this is done
$callback = "
function()
{
_data = ajaxac_receivejsarray(%1\$s.responseText);
populateSelect(%2\$s, _data);
enableCountry(%2\$s, %3\$s, %4\$s);
}
";
$callback = sprintf($callback,
$widget->getHookName(),
$this->getHookName('country'),
$this->getHookName('region'),
$this->getHookName('city'));
return $callback;
}
/**
* Handles the value in the country dropdown being changed
*/
function event_countrychange(&$widget, $event)
{
// when a country is changed, we need to fetch the associated
// regions, so we create a HTTP request to do so. The action to
// do this is getregions, and we also need to pass to it the
// selected country in the 'c' request parameter. Because this
// country value is stored in a JavaScript variable, we pass the name of
// the variable. And finally, once the request is complete, we
// need to insert the region data, which is done in event_handleregions
$xmlhttp = $this->XMLHttpRequest('r2', AJAXAC_METH_GET);
$xmlhttp->setFilenameFromString($this->getApplicationUrl('getregions'));
$xmlhttp->addParamFromHookValue('c', $this->getHookName('country'));
$xmlhttp->addEvent(AJAXAC_EV_ONXMLHTTPSUCCESS, 'handleregions');
// callback description:
// 1. Check if a country has been selected
// 2. If not, set the state back to needing to select a country as when countries were initially loaded
// 3. If so, set the state of region/city to indicate that a region is being loaded, and perform the subrequest
//
$callback = "
function()
{
try {
if (this.selectedIndex == 0)
enableCountry(this, %2\$s, %3\$s);
else {
loadingRegion(%2\$s, %3\$s);
%4\$s
}
}
catch (e) { }
return false;
}
";
$callback = sprintf($callback,
$this->getHookName('country'),
$this->getHookName('region'),
$this->getHookName('city'),
$xmlhttp->getJsCode());
return $callback;
}
/**
* Deals with the returned region data once the XMLHttp request is completed
*/
function event_handleregions(&$widget, $event)
{
// callback description:
// 1. Firstly checks the the HTTP subrequest is complete
// 2. Next checks that it was a valid 200 response header
// 3. Assigns the returned JavaScript array from action_getregions() to _data
// 4. Populates the region dropdown with what is in _data
// 5. Set the region/city dropdowns to their required state for region data just being loaded
// (this is basically, tell user to pick a region, and disable city until this is done
$callback = "
function()
{
_data = ajaxac_receivejsarray(%1\$s.responseText);
populateSelect(%2\$s, _data);
enableRegion(%2\$s, %3\$s);
}
";
$callback = sprintf($callback,
$widget->getHookName(),
$this->getHookName('region'),
$this->getHookName('city'));
return $callback;
}
/**
* Handles the value in the region dropdown being changed
*/
function event_regionchange(&$widget, $event)
{
// when a region is changed, we need to fetch the associated
// cities, so we create a HTTP request to do so. The action to
// do this is getcities, and we also need to pass to it the
// selected country in the 'c' request parameter and the selected
// region in the 'r' request parameter. Because the country and
// region values are stored in JavaScript variables, we pass the name of
// the variables. And finally, once the request is complete, we
// need to insert the city data, which is done in event_handlecities
$xmlhttp = $this->XMLHttpRequest('r3', AJAXAC_METH_GET);
$xmlhttp->setFilenameFromString($this->getApplicationUrl('getcities'));
$xmlhttp->addParamFromHookValue('c', $this->getHookName('country'));
$xmlhttp->addParamFromHookValue('r', $this->getHookName('region'));
$xmlhttp->addEvent(AJAXAC_EV_ONXMLHTTPSUCCESS, 'handlecities');
// callback description:
// 1. Check if a region has been selected
// 2. If not, set the state back to needing to select a region as when regions were initially loaded
// 3. If so, set the state of city to indicate that a city is being loaded, and perform the subrequest
//
$callback = "
function()
{
try {
if (this.selectedIndex == 0)
enableRegion(%2\$s, %3\$s);
else {
loadingCity(%3\$s);
%1\$s
}
}
catch (e) { }
return false;
}
";
$callback = sprintf($callback,
$xmlhttp->getJsCode(),
$this->getHookName('region'),
$this->getHookName('city'));
return $callback;
}
/**
* Deals with the returned city data once the XMLHttp request is completed
*/
function event_handlecities(&$widget, $event)
{
// callback description:
// 1. Firstly checks the the HTTP subrequest is complete
// 2. Next checks that it was a valid 200 response header
// 3. Assigns the returned JavaScript array from action_getcities() to _data
// 4. Populates the city dropdown with what is in _data
// 5. Set the city dropdowns to its required state for city data just being loaded
// (this is basically, tell user to pick a city
$callback = "
function()
{
_data = ajaxac_receivejsarray(%1\$s.responseText);
populateSelect(%2\$s, _data);
enableCity(%2\$s);
}
";
$callback = sprintf($callback,
$widget->getHookName(),
$this->getHookName('city'));
return $callback;
}
function event_handlefailure(&$widget, $event)
{
$callback = "
function()
{
alert('An error occurred loading the data');
}
";
return $callback;
}
/**
* Retrieve the list of countries and return them in an array
*/
function getCountries()
{
// uncomment this to notice the disabling/loading status update
usleep(300000);
return array_keys($this->getNestedData());
}
/**
* Retrieve the list of regions for the given country, or null if
* the country was not found
*/
function getRegions($country)
{
// uncomment this to notice the disabling/loading status update
usleep(300000);
$data = $this->getNestedData();
if (isset($data[$country]))
return array_keys($data[$country]);
return null;
}
/**
* Retrieve the list of cities for the given country and region, or
* null if the country or region were not found
*/
function getCities($country, $region)
{
// uncomment this to notice the disabling/loading status update
usleep(300000);
$data = $this->getNestedData();
if (isset($data[$country][$region]))
return $data[$country][$region];
return null;
}
/**
* A utility function to parse our location data into a usable format
*/
function getNestedData()
{
$ret = array();
$lines = file($this->data);
foreach ($lines as $line) {
$line = trim($line);
if (strlen($line) == 0)
continue;
$parts = explode(',', $line, 3);
$country = $parts[0];
$region = $parts[1];
$city = $parts[2];
$ret[$country][$region][] = $city;
}
return $ret;
}
}
?>