Growler notifications in the Magento backend
<!-- no magic here, just the absolute necessary to get started --> <?xml version="1.0"?> <config> <modules> <Magenticians_GrowlerMessages> <version>0.0.1</version> </Magenticians_GrowlerMessages> </modules> <global> <models> <growlermessages> <class>Magenticians_GrowlerMessages_Model</class> </growlermessages> </models> </global> </config>Because we want to update the backend layout, aliased as adminhtml, we will tell Magento the module has a layout-file with updates to be processed. growlermessages.xml contains layout definitions for the default adminhtml skin, so it finds its place in /app/design/adminhtml/default/default/layout.
<!-- ... --> </global> <adminhtml> <layout> <updates> <growlermessages> <file>growlermessages.xml</file> </growlermessages> </updates> </layout> </adminhtml> </config>Its contents are very straight forward: telling the layout processor we want to include Growler its footprint on every page:
<?xml version="1.0"?> <layout> <default> <reference name="head"> <action method="addItem"><type>js</type><name>growlermessages/growler.js</name></action> <action method="addItem"><type>skin_css</type><name>growlermessages/css/growler.css</name></action> </reference> </default> </layout>Last but not least, we add a template-file to the layout definition which can be used to render our custom JavaScript for initiating Growler. We assign it to the js block, an adminhtml section designated for JavaScript to be included, located near the end of the document. This template-file is retrieved relative from /app/design/adminhtml/default/default/template. Because we do not need a custom block, we will borrow the default adminhtml/template behavior.
<!-- ... --> </reference> <reference name="js"> <block type="adminhtml/template" as="growlermessages_js" name="growlermessages_js" template="growlermessages/js/growler.phtml" /> </reference> </default> </layout>As soon as you put a standard definition of the just created module in /app/etc/modules and look at the source code of any backend page, you should see the Growler-footprint in the section. Because the js/growler.phtml file is probably still empty, put some “#testing” string in it and you should be able to find it in the source, too.
<events> <controller_action_layout_generate_blocks_before> <observers> <magenticians_growlermessages_relaymessages> <class>growlermessages/Observer</class> <method>relayMessages</method> </magenticians_growlermessages_relaymessages> </observers> </controller_action_layout_generate_blocks_before> </events>We are naming our observer its method relayMessages because it moves the original messages storage to a different place, namely the messages_collection key in the Magento registry:
class Magenticians_GrowlerMessages_Model_Observer { public function relayMessages(Varien_Event_Observer $observer) { // Retrieve the messages collection from adminhtml session and put it in the registry // true parameter passed to getMessages will make sure they get cleared from the session Mage::register('messages_collection', Mage::getSingleton('adminhtml/session')->getMessages(true)); } }Navigating through the backend and performing some actions which used to spawn messages, like saving store configuration, should now be very silent.
g.info('Growl, growl, here is some info!', {classname: 'plain'});Before we get to these calls, we start our growler.phtml file with checking whether there are any messages. Seeing as the template is included on every page load in the backend, we don’t want to be held accountable for slowing down page load, heh.
<?php $_messages = Mage::registry('messages_collection'); if (is_null($_messages)) { return; } $_messages = $_messages->getItems(); if (empty($_messages)) { return; } ?>$_messages is now an array with instances of Mage_Core_Model_Message_Abstract, Mage_Core_Model_Message_Success for example. Because all of its fields are protected, json_encode will not function properly. We have to build a proper array ourselves.
$_messagesData = array(); foreach ($_messages as $_message) { /** @var $_message Mage_Core_Model_Message_Abstract */ $_messagesData[] = array( 'type' => $_message->getType(), 'text' => $_message->getText() ); } ?> <script type="text/javascript"> // To be continued... </script>Note that messages have a few other data points, but type and text are all we need. isSticky is such a field, but we haven’t found any occurrences in the Magento code base of messages having that field set to something other than the default value.
<script type="text/javascript"> var messages = <?php echo json_encode($_messages); ?>; </script>Because the Growler message types (error, warn, growl and info) do not directly translate to Magento its message types (error, warning, notice and success), we will have to use a lookup-table:
// Magento -> Growler var typeTranslation = { error: 'error', warning: 'warn', notice: 'growl', success: 'info' };The last piece of the puzzle is iterating over messages, calling the appropriate method as defined in typeTranslation and supply the message its text. Each growl will have a classname of plain for the appropriate CSS styling to get attached.
var growler = new Growler({}); for (var i = 0; i < messages.length; i++) { var message = messages[i]; growler[typeTranslation[message.type]](message.text, {classname: 'plain'}); }
public function relayMessages(Varien_Event_Observer $observer) { if (Mage::app()->getRequest()->isXmlHttpRequest()) { return; } Mage::register('messages_collection', Mage::getSingleton('adminhtml/session')->getMessages(true)); }AJAX requests which spawn messages, should now start appearing as original again. We could leave it like this, or start using some magic to even get messages pulled in via AJAX, working with Growler.
<events> <http_response_send_before> <observers> <magenticians_growlermessages_altermessagesresponse> <class>growlermessages/Observer</class> <method>alterMessageResponse</method> </magenticians_growlermessages_altermessagesresponse> </observers> </http_response_send_before> </events>In the observer, non-AJAX requests are ignored. All other requests their responses will have a body-check for valid JSON. If this is the case, we will check the body its contents for either a ‘message’ or ‘messages’ key. The contents will be replaced with a JSON encoded array of messages which can be easily fed to Growler:
public function alterMessageResponse(Varien_Event_Observer $observer) { if (! Mage::app()->getRequest()->isXmlHttpRequest()) { // Ignore non-AJAX requests return; } $responseBody = $observer->getEvent()->getResponse()->getBody(); // Attempt to decode the response object, non-JSON responses will be skipped try { $responseBodyDecoded = Mage::helper('core')->jsonDecode($responseBody); } catch (Zend_Json_Exception $e) { return; } // Intercept our target keys and replace with our own JSON encoded array foreach (array('message', 'messages') as $target) { if (isset($responseBodyDecoded[$target])) { $responseBodyDecoded[$target] = json_encode(Mage::helper('growlermessages')->getMessages()); } } // Ship our modified body to the response $observer->getEvent()->getResponse()->setBody(Mage::helper('core')->jsonEncode($responseBodyDecoded)); }Note that the Mage::helper(‘growlermessages’)->getMessages() call is nothing more than the code we earlier wrote directly in js/growler.phtml, wrapped in a helper class.
Ajax.Responders.register({ onComplete: function() { // Always hide the messages container because it might contain a JSON-blob $('messages').hide(); if (! $('messages').innerText.isJSON()) { return; } var messages = $('messages').innerText.evalJSON(); for (var i = 0; i < messages.length; i++) { var message = messages[i]; growler[typeTranslation[message.type]](message.text, {classname: 'plain'}); } } });Above, we always hide the #messages container as it might contain a JSON-blob. Then, we check whether the container its contents is valid JSON and if so, evaluate it and save the result in the messages variable. The for-loop we use, should look familiar as we used it earlier. And finally, doing anything over AJAX which spawns messages, should now also show Growler messages.
Your greatest possible competitive advantage can be your clients and the interactions they have with… Read More
Digital marketing KPIs are measurable values that marketing teams use to track progress toward desired… Read More
In today's digital age, fraud poses a significant threat to businesses of all sizes. As… Read More
Financial crimes continue to evolve and proliferate in our increasingly digital, global economy. From complex… Read More
In the highly competitive modern workplace, trust, and employee loyalty are crucial factors for long-term… Read More
In the ever-evolving world of small business developing and implementing effective marketing strategies is critical to… Read More
View Comments
Very nice blog post - thanks!