Categories


Archives


Recent Posts


Categories


Magento Javascript Events

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

This entry is part 1 of 1 in the series Magento Javascript Code. This is the first post in the series.
  • Magento Javascript Events

Any Magento developer worth their salt is familiar with Magento’s PHP based event system. This critical piece of functionality provides for unobtrusive tweaks to Magento’s functionality. Less talked about is Magento’s frontend event system used by the admin console. This article seeks to shed some light on this little used, but potentially useful, javascript event system.

Even if you never plan on writing a single line of event based javascript code for your store, understanding Magento’s frontend events is a good introduction to its javascript based systems code, and this knowledge is critical if you need to debug anything javascript related in Magento.

Admin Console Javascript

Our first step is getting some javascript onto an admin console page. While not the point of this article, the following code serves as a nice introduction to Magento’s backend event and layout system. Since we’re here to talk about javascript you can blindly key-in or download
the code without thinking too much about it. There’s lots of ways to get javascript onto an admin page, none of them are correct.

First, we’re going to create and configure a blank module named Pulsestorm_Javascriptevent to hold our code. The etc/modules files declares the module

<!-- File: app/etc/modules/Pulsestorm_Javascriptevent.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<config>
    <modules>
        <Pulsestorm_Javascriptevent>
            <active>true</active>
            <codePool>local</codePool>
        </Pulsestorm_Javascriptevent>
    </modules>
</config>

and the Pulsestorm/Javascriptevent/etc/config.xml file adds the module’s specific configuration to the Magento system.

<!-- File: app/code/local/Pulsestorm/Javascriptevent/etc/config.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<config>
    <modules>
        <Pulsestorm_Javascriptevent>
            <version>0.1.0</version>
        </Pulsestorm_Javascriptevent>
    </modules>
    <global>
        <models>
            <pulsestorm_javascriptevent>
                <class>Pulsestorm_Javascriptevent_Model</class>
            </pulsestorm_javascriptevent>
        </models>
    </global>
</config>

Next, we’ll configure an observer for the controller_action_layout_generate_blocks_after event. This is the event that fires after Magento has finished processing the layout update XML, but before output is rendered. It’s the perfect place to add a block with some custom javascript code for every page. Notice that we’re putting this event in the adminhtml section of the config, meaning it will only fire when viewing the admin console application, and NOT the frontend cart application.

#File: app/code/local/Pulsestorm/Javascriptevent/etc/config.xml
<config>
    <!-- ... -->
    <adminhtml>
        <events> 
            <controller_action_layout_generate_blocks_after>
                <observers>
                    <pulsestorm_javascriptevent_addjs>
                        <type>singleton</type>
                        <class>pulsestorm_javascriptevent/observer</class>
                        <method>addJavascriptBlock</method>
                    </pulsestorm_javascriptevent_addjs>
                </observers>
            </controller_action_layout_generate_blocks_after>
        </events>        
    </adminhtml>
</config>

After we add the event to our config.xml file, we’ll need to create our observer object and method.

#File: app/code/local/Pulsestorm/Javascriptevent/Model/Observer.php    
<?php
class Pulsestorm_Javascriptevent_Model_Observer
{
    public function addJavascriptBlock($observer)
    {
        exit(__METHOD__);
    }
}    

You’ll notice we’ve placed an exit in our code above. That’s because it’s always a good idea to periodically check your work when doing any complex Magento configuration. Accessing a backend page with the above configuration will result in the following being output to a white browser screen

Pulsestorm_Javascriptevent_Model_Observer::addJavascriptBlock

Once we get the observer firing, our next step is adding a custom block to the layout object.

#File: app/code/local/Pulsestorm/Javascriptevent/Model/Observer.php    
<?php
class Pulsestorm_Javascriptevent_Model_Observer
{
    public function addJavascriptBlock($observer)
    {
        $controller = $observer->getAction();
        $layout = $controller->getLayout();
        $block = $layout->createBlock('core/text');
        $block->setText(
        '<script type="text/javascript">
            function main_pulsestorm_hellojavascript()
            {
                alert("Foo");
            }
            main_pulsestorm_hellojavascript();
        </script>'
        );        
        $layout->getBlock('js')->append($block);            
    }
}    

The above code fetches the controller object from the event observer with the following

#File: app/code/local/Pulsestorm/Javascriptevent/Model/Observer.php    
$controller = $observer->getAction();

and then fetches the layout singleton object from the controller object

#File: app/code/local/Pulsestorm/Javascriptevent/Model/Observer.php    
$layout = $controller->getLayout();

and then uses the layout object to create a text block and set its content to be a single javascript alert script

#File: app/code/local/Pulsestorm/Javascriptevent/Model/Observer.php    

$block = $layout->createBlock('core/text');
$block->setText(
'<script type="text/javascript">
    function main_pulsestorm_hellojavascript()
    {
        alert("Foo");
    }
    main_pulsestorm_hellojavascript();
</script>'
);        

and finally, it fetches the block named js from the layout object, and appends our block to it.

#File: app/code/local/Pulsestorm/Javascriptevent/Model/Observer.php    

$layout->getBlock('js')->append($block);            

The js block is a special adminhtml block created specifically for this sort of hook.

With all of the above in place, loading any page on your admin console will result in a javascript alert being displayed.

Working with javascript in a PHP string is a little janky, so let’s create a phtml template for our javascript

#File: app/design/adminhtml/default/default/template/pulsestorm_javascriptevent/hello.phtml
<script type="text/javascript">
    function main_pulsestorm_hellojavascript()
    {
        alert("bar");
    }
    main_pulsestorm_hellojavascript();
</script>    

and then modify our event observer to use an admin template block to render the javascript.

#File: app/code/local/Pulsestorm/Javascriptevent/Model/Observer.php    

<?php
class Pulsestorm_Javascriptevent_Model_Observer
{
    public function addJavascriptBlock($observer)
    {
        $controller = $observer->getAction();
        $layout = $controller->getLayout();
        $block = $layout->createBlock('adminhtml/template');
        $block->setTemplate('pulsestorm_javascriptevent/hello.phtml');        
        $layout->getBlock('js')->append($block);
    }
}

Refreshing the page will cause an alert with the text “bar” to be displayed. More importantly, we now have the ability to run javascript on any page which will let us explore the front-end event system.

Prototype vs. jQuery vs. Magento

Magento’s admin console uses Prototype as its sole javascript library. This often leaves frontend developers used to jQuery grumbling about an unfamiliar, less capable, framework.

While we’re not going to get into that particular long standing debate in the Magento community, I do have a bit of context that might reassure jQuery developers.

Prototype developers are often equally mystified by Magento’s frontend code.

That’s because, much like they did with PHP, the core team has used Prototype as a base for custom UI and programming features. Knowing Prototype doesn’t mean you automatically know Magento’s code conventions.

The typical patten for a Magento javascript system feature is for a global object to be declared in an external Javascript file. By creating a global variable, Magento ensures developers working in any part of the system will have easy access to it.

While this practice may rankle those on the bleeding edge of “Web Standards” efforts, it’s still (in 2012) a common pattern in IT and business applications. Like any software system, it’s best to learn the conventions, work within them, and then look for ways to incrementally improve the system and your own team’s processes.

Simple Event Example

The javascript object we’re interested in today is

varienGlobalEvents;

If you use a javascript debugger to log the varienGlobalEvents object, you’ll see something like this. (be sure to read this quick warning about console.log)

console.log(varienGlobalEvents);
klass
    arrEvents: Object
    eventPrefix: ""
    __proto__: Object    

The keyword klass is the dead giveaway that this is a javascript object created with the Prototype framework. This object is responsible for managing the Magento frontend event system. Although it’s beyond the scope of this article, if you’re curious in how this system was implemented you can find the source code in ./js/mage/adminhtml/events.js

What is well within the scope of this article is using the event system. Like any event system, there’s two parts. Certain system code will fire an event when something important happens. To fire an event, use the following code

#File: app/design/adminhtml/default/default/template/pulsestorm_javascriptevent/hello.phtml
<script type="text/javascript">
    function main_pulsestorm_hellojavascript()
    {
        varienGlobalEvents.fireEvent('somethingHappened');
    }
    main_pulsestorm_hellojavascript();
</script>   

If you refresh your page with the above code in place … nothing happens. That’s because firing an event is a silent thing. You’re saying to the system

Hey, this thing happened. Let anyone who’s interested know about it.

That’s where the second part comes in. We need to listen to, or subscribe to, this event. In varienGlobalEvent speech, we need to attach an event handler. You can do that with the following code.

#File: app/design/adminhtml/default/default/template/pulsestorm_javascriptevent/hello.phtml
<script type="text/javascript">
    function main_pulsestorm_hellojavascript()
    {
        varienGlobalEvents.attachEventHandler('somethingHappened', function(){
            alert("Hey, look at me.");
        });

        varienGlobalEvents.fireEvent('somethingHappened');
        console.log("Done");
    }
    main_pulsestorm_hellojavascript();
</script>   

The attachEventHandler method accepts an event name (somethingHappened) and a javascript callback (in our case, an inline javascript function). When the system fires the event we’re listening for, the underlying system code will call our method

When you’re writing system code, you can also pass a single argument to your event subscribers. Consider the following

#File: app/design/adminhtml/default/default/template/pulsestorm_javascriptevent/hello.phtml
<script type="text/javascript">
    function main_pulsestorm_hellojavascript()
    {        
        varienGlobalEvents.attachEventHandler('somethingHappened', function(one){
            alert("Hey, look at me.");
            alert(one);
        });

        varienGlobalEvents.fireEvent('somethingHappened','uno');
        console.log("Done");        
    }
    main_pulsestorm_hellojavascript();
</script>       

The results of the code above is an alert box with the the text “uno” being displayed after the “Hey, look at me” alert box. If you need to send more than one paramater to your subscribers, use a javascript object literal ({}) to pass the paramaters along.

A Less Silly Example

The above code is, of course, a little silly. Let’s try something practical. Replace the above code with the following

#File: app/design/adminhtml/default/default/template/pulsestorm_javascriptevent/hello.phtml

<script type="text/javascript">
    function main_pulsestorm_hellojavascript()
    {        
        varienGlobalEvents.attachEventHandler('showTab', function(arg){
            console.log(arg.tab);
        });        
    }
    main_pulsestorm_hellojavascript();
</script>   

and then go to any entity editing form in your system (i.e. edit an individual product). You’ll notice that each time you switch tabs in the UI form, something like following is sent to the console output

<a href="#" id="product_info_tabs_group_80" name="group_80" title="General" class="tab-item-link active"> ...

That’s because a Magento engineer setup a call to

varienGlobalEvents.fireEvent('showTab')

in the Javascript UI code for the form. This is potentially useful for manipulating existing UI element behavior in the system, or unobtrusively adding custom behavior to your own UI tabs.

List Frontend Admin Console Events

At this point you’re probably wondering what sort of frontend events we have access to. Using a bit of static analysis (which is a fancy way of saying grep), I pulled a list of varienGlobalEvents fired by core system code

File: app/design/adminhtml/default/default/template/customer/tab/addresses.phtml
    Event Name: address_country_changed
    Event Name: address_country_changed

File: js/mage/adminhtml/browser.js
    Event Name: tinymceChange

File: js/mage/adminhtml/form.js
    Event Name: formSubmit
    Event Name: formValidateAjaxComplete
    Event Name: address_country_changed

File: js/mage/adminhtml/grid.js
    Event Name: gridRowClick
    Event Name: gridRowDblClick

File: js/mage/adminhtml/tabs.js
    Event Name: moveTab
    Event Name: moveTab
    Event Name: showTab
    Event Name: tabChangeBefore
    Event Name: hideTab

File: js/mage/adminhtml/wysiwyg/tiny_mce/setup.js
    Event Name: tinymceSubmit
    Event Name: tinymcePaste
    Event Name: tinymceBeforeSetContent
    Event Name: tinymceSetContent
    Event Name: tinymceSaveContent
    Event Name: tinymceChange
    Event Name: tinymceExecCommand
    Event Name: open_browser_callback

js/mage/adminhtml/wysiwyg/widget.js
    Event Name: tinymceChange

While there’s nothing too earth shattering in there, I’ve found the formSubmit method to be useful, and the tinymce related events look like they’re potentially helpful for developers customizing the Magento editing experience.

Like their backend cousins, Magento’s events weren’t designed to be used for a particular reason. Instead they’re general hooks for you own ideas, features and imagination.

Wrap UP

While many of the design patterns introduced to computer science in the mid-90s are esoteric and not commonly used in day to day web development, it’s clear that the event/observer pattern is one that’s managed to rise above the rabble and worm its warm into all sorts of programming niches.

As the self-hosted Magento project evolves into a system for established companies to extend the reach of their e-commerce operations it’s increasingly common for third party developers to customize or tweak the backend admin console to meet their client’s needs. Leveraging the Prototype based event system is a great way for you and your team to safely extend the Javascript/UI code in the backend system. Your code will be upgrade resistant and kept separate form the core javascript files.

Your clients, and your long term support team, will thank you.

Originally published June 29, 2012