- 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.