Magento region updater overriding

The Magento RegionUpdater is a prototype class, which operates with simple but very useful feature such as country/region options in the multiple Magento address forms. The main goal of this article is to show the way of the soft prototype objects manipulation and, especially, Magento prototype objects without updating
standard Varien JavaScript files.

We will describe how to do it using the simple, but at the same time, really useful task: decorate standard select bars without loosing the functionality in country/region section of an address form.

First of all, let’s choose the plugin that we will use to make our select bars look nicer. There is a huge variety of plugins, especially for jQuery.
Let’s assume that we use one of them, so, you can choose any plugin you want. In this case, we’ll call it “nicePlugin” and transform our select bars by using syntax like:

<script type="text/javascript">
	jQuery('selector').nicePlugin();
</script>

The fact is that you can use any other plugins from Prototype or other libraries and change syntax respectively. Just in case, let’s remind the way how to insert our plugin to Magento. You can start from unpacking your plugin to BaseDir/skin/frontend/yourpackage/yourtheme/js. Then, move the styles and images files of your plugin to BaseDir/skin/frontend/yourpackage/yourtheme/css. After this, add plugin files (and jQuery if it is not there already) in your custom module layout, page.xml or
local.xml in BaseDir/app/design/frontend/yourpackage/yourtheme/layout:

<default>
	<reference name="head">
		<action method="addItem">
			<type>skin_js</type>
			<name>js/jquery-1.10.2.min.js</name>
		</action>
		<action method="addItem">
			<type>skin_js</type>
			<name>js/nicePlugin.js</name>
		</action>
		<action method="addItem">
			<type>skin_css</type>
			<name>css/nicePlugin.css</name>
		</action>
	</reference>
</default>

Pay attention that the base/default theme is not being edited, because we are customizing a view and it’s the right way to be dependent on your custom theme only.

So, good news are that the plugin is set, well then, we are ready to check the RegionUpdater and attempt to modify it. This class prototype is defined in BaseDir/js/varien/form.js and initialized right after a form, containing the country/region section. Here is an example from
BaseDir/app/design/frontend/base/default/template/customer/address/edit.phtml:

<script type="text/javascript">
//<![CDATA[
    var dataForm = new VarienForm('form-validate', true);
    new RegionUpdater('country', 'region', 'region_id', <?php echo $this->helper('directory')->getRegionJson() ?>, undefined, 'zip');
//]]>
</script>

This way to initiate RegionUpdater makes it self containing without possibility of a direct class methods call. This class simply puts its own “change” event listener to a selector passed as the first argument on the class initiation. As you can see, we have two problems here:

  1. We can not modify RegionUpdater class instance methods.
  2. Our “nicePlugin” usually hides the original select bar, then shows its version in more custom HTML markup, and changes the original select’s value via JavaScript method (which does not fire RegionUpdater events).

Fortunately, the first problem is easy to solve. But it requires some Magento theme development. Here we simply make a class object instance from RegionUpdater prototype as on the checkout page, for example. Just make a copy of the target form template in app/design/frontend/yourpackage/yourtheme/path/name.phtml. Note, that the path and name should be identical to base/default theme’s ones, otherwise, you will need to redefine it in layouts. Then, simply change:

new RegionUpdater('country', 'region', 'region_id', <?php echo $this->helper('directory')->getRegionJson() ?>, undefined, 'zip');

to:

var RegionUpdater = new RegionUpdater('country', 'region', 'region_id', <?php echo $this->helper('directory')->getRegionJson() ?>, undefined, 'zip');

As a result, we can access its methods.

But, we need to be careful here and modify only visible inputs and selects in the address forms. Our “nicePlugin” can transform hidden selects, as a result, RegionUpdater and VarienForm functionality might be broken. We’d recommend to make it the following way (add this code to your custom JavaScript file, connected to this page):

	jQuery(document).ready(function(){
		jQuery("select.validate-select").nicePlugin();
	})

The second problem should be solved by manual triggering “update” method of our RegionUpdater instance only if the first one has been already solved. Simply add this code to the target template file (use <script> tag) or custom JavaScript file, used on this page:

    jQuery(document).ready(function(){
        jQuery("selector").change(function(){
            RegionUpdater.update();
        })
    })

We will move this code snippet to our new template file in the second part of this article.

Now, it is time to extend the “update” method of RegionUpdater and we’d recommend to do it by
wrapping the original method. Add this after the RegionUpdater initiation:

<script type="text/javascript">
//<![CDATA[
	RegionUpdater.update = RegionUpdater.update.wrap(function(parentMethod){
	
		//you can do your stuff here before update method fires
		//we search and remove added niced select markup for region 
		//select, which is located in the same parent tag with select
		//and class which indicates that region select was transformed
		var arr = Array.prototype.slice.call( RegionUpdater.regionSelectEl.parentElement.children );
		arr.each(function(item){
			if( item.className.indexOf('nicePlugin-class') != -1 ) {
				jQuery("#"+RegionUpdater.regionSelectEl.id.replace( /(:|\.|\[|\])/g, "\\$1" )).removeClass('nicePlugin-transformed-class');
				item.remove();
			}
		});
		//parent "update" method call
		parentMethod();
		
		//do your stuff after "update" method fires
		//and don't forget to transform region select,
		//if it becomes visible
		if(RegionUpdater.regionSelectEl.visible()){
			$j("#"+RegionUpdater.regionSelectEl.id.replace( /(:|\.|\[|\])/g, "\\$1" )).nicePlugin();
		}
	});
//]]>
</script>

That’s all. Furthermore, repeat these updates for customer address edit page, cart, checkout and other custom pages which contain form with country\region section. Or you are able to make this update universal for all current address forms using the following steps:

Step 1. Create your custom module (or choose one of already created) to add RegionUpdater overridden functionality. Here you may also create your own layout.

Step 2. Also, we will need a block to make changes on the checkout and custom forms. Put it to BaseDir/app/code/local/Company/Module/Block/Form:

<?php

class Company_Module_Block_Form_RegionUpdater extends Mage_Core_Block_Template
{
	//attribute for different RegionUpdater instances
    protected $_prefix = '';

    public function setPrefix( $prefix )
    {
        $this->_prefix = $prefix;
    }

    public function getPrefix()
    {
        return $this->_prefix;
    }

    /**
	* method to define country select selector for different pages
	* double backslash is required to screen the : symbol in jQuery 
	* and this double backslash requires screening too,lol 
	* @return string
	*/
    public function getSelectId() {
        switch($this->getPrefix()){
            case 'shipping': return 'shipping\\\\:country_id';
            case 'billing': return 'billing\\\\:country_id';
            default : return 'country';

        }
    }
}

Step 3. After, we should create the template of our block. For that put it in the
BaseDir/app/design/frontend/yourpackage/yourtheme/module/form/regionUpdater.phtml. It will include the examples shown above, which (depending on the place) were inserted by using prefix:

<?php
/**
 * @var $this Mage_Core_Block_Template
 */
$_prefix = $this->getPrefix();
?>
<script type="text/javascript">
    //<![CDATA[
    <?php echo $_prefix;?>RegionUpdater.update = <?php echo $_prefix;?>RegionUpdater.update.wrap(function(parentMethod){
        var arr = Array.prototype.slice.call( <?php echo $_prefix;?>RegionUpdater.regionSelectEl.parentElement.children );
        arr.each(function(item){
            if( item.className.indexOf('nicePlugin-class') != -1 ){
                $j("#"+<?php echo $_prefix;?>RegionUpdater.regionSelectEl.id.replace( /(:|\.|\[|\])/g, "\\$1" )).removeClass('nicePlugin-transformed-class');
                item.remove();
            }
        });
        parentMethod();
        if(<?php echo $_prefix;?>RegionUpdater.regionSelectEl.visible()){
            $j("#"+<?php echo $_prefix;?>RegionUpdater.regionSelectEl.id.replace( /(:|\.|\[|\])/g, "\\$1" )).nicePlugin();
        }
    });
    $j(document).ready(function(){
        $j("#<?php echo $this->getSelectId(); ?>").change(function(){
            <?php echo $_prefix;?>RegionUpdater.update();
        })
    })
    //]]>
</script>

Step 4. Now, we will use a layout update to make our RegionUpdate class instance dependent on the current handle. Note, that we add two updates to the checkout page (each for its country/region section) and set a prefix to make a difference between two created region updater instances. Then, we are adding our updates to the list block types at the end of the page, so they will be called automatically. Update the layout of your module with this:

    <customer_address_form>
        <reference name="content">
            <block type="yourblock/form_regionupdater" name="region.updater" template="module/form/regionUpdater.phtml" after="-" />
        </reference>
    </customer_address_form>
    <checkout_cart_index>
        <reference name="content">
            <block type="yourblock/form_regionupdater" name="region.updater" template="module/form/regionUpdater.phtml" after="-" />
        </reference>
    </checkout_cart_index>
    <checkout_onepage_index>
        <reference name="content">
            <block type="yourblock/form_regionupdater" name="shipping.region.updater" template="module/form/regionUpdater.phtml" after="-" >
                <action method="setPrefix"><prefix>shipping</prefix></action>
            </block>
            <block type="yourblock/form_regionupdater" name="billing.region.updater" template="module/form/regionUpdater.phtml" after="-" >
                <action method="setPrefix"><prefix>billing</prefix></action>
            </block>
        </reference>
    </checkout_onepage_index>

We hope this information is useful not only for the RegionUpdater customizing, but for the most of standard Magento Prototype objects operating.

We will be glad to provide you with the answers on all additional questions in the comments below.