How you could build your Magento extensions without view files

Featured Image

The main idea behind this article is something I previously wrote, making your extensions more distributable, less error prone, less depended, less intrusive. Such extensions are then more “safer” to be fetched and installed via Magento Connect. When I say safer I am thinking in terms of your extension not breaking someone live store, etc.

I would dare to say that one of the most annoying and dangerous areas of extension development are the theme view files. For example, imagine an extension that adds some “color switcher” like functionality on the product view page.

Usually extension developer will create his alternative of /app/design/frontend/default/default/template/catalog/product/view.phtml or /app/design/frontend/default/default/template/catalog/product/view/media.phtml file placing it under the default theme.

This approach is bad for at least two reasons:
- what if some other extension already does the same thing
- what if theme developer deletes it and places its own code in it

Personally I like my extensions code centralized. When I say centralized, I am referring to keeping all of my extension code under the one folder, the /app/code/community/ folder, depending on the name of the company and module namespace.

Let me give you a simple example of simple extension that demonstrates powerful concept of “adding things unobtrusively”. My extension is called “Selectify“ and it will be located under the “Inchoo” company folder.

/app/code/community/Inchoo/Selectify/

Extension will be turning simple quantity input fields from product view page into dropdown/select fields, allowing selections of 1, 2, 3 quantity to be selected. Why would anyone want to do that? Most likely it would not, but lets just imagine we need to do it.

Easiest approach would be simply to open/create appropriate /app/design/frontend/default/default/template/catalog/product/view/addtocart.phtml file and just replace the input text field element to select element.

The problem with that approach is that we are developing extension, not modifying the theme. We do not care on the look & feel of the theme or theme name or, etc. So, in the spirit of the “unobtrusively” we will not apply this approach.

We will apply following logic to the approach:

  • Find the file that needs to be modified /app/design/frontend/default/default/template/catalog/product/view/addtocart.phtml
  • Think it trough how we can change it without touching it (obviously via some simple JavaScript in this case)
  • Apply the change by hooking into some appropriate event

Now, the interesting part is the “hooking into some appropriate event”. Each child of Mage_Core_Block_Abstract class triggers an event that looks like this:

Mage::dispatchEvent('core_block_abstract_to_html_after', array('block' => $this, 'transport' => self::$_transportObject));

What this means is that simply by observing that event you can change the raw HTML output of it, as the “transport” holds the HTML content of the given block. So basically, all you need is an event observer targeted specifically at some block by it’s name. Reminds me on the good old WordPress stuff, LOL.

Step 1: Declare observer in config.xml file

<frontend>
	<events>
		<core_block_abstract_to_html_after>
			<observers>
				<inchoo_selectify>
					<class>inchoo_selectify/observer</class>
					<method>convertQtyInputToSelect</method>
				</inchoo_selectify>
			</observers>
		</core_block_abstract_to_html_after>
	</events>
</frontend>

Step 2: Write a block class that will hold some essential logic or HTML code itself

class Inchoo_Selectify_Block_QtyInputToSelect extends Mage_Core_Block_Text
{
    protected $_nameInLayout = 'selectify.qty_input_to_select';
    protected $_alias = 'qty_input_to_select';
 
    public function setPassingTransport($transport)
    {
        $this->setData('text', $transport.$this->_generateQtyInputToSelectHtml());
    }
 
    private function _generateQtyInputToSelectHtml()
    {
        return '
            <script type="text/javascript">
            //<![CDATA[
 
            document.observe("dom:loaded", function() {
                $("qty").replace(\'<select class="input-text qty" title="Qty" value="1" id="qty" name="qty"><option selected="selected" value="1">1</option><option value="2">2</option><option value="3">3</option></select>\');
            });
 
            //]]>
            </script>
        ';
    }
}

Step 3: Handle the actual event by some observer code

class Inchoo_Selectify_Model_Observer
{
    const MODULE_NAME = 'Inchoo_Selectify';
 
    public function convertQtyInputToSelect($observer = NULL)
    {
        if (!$observer) { 
            return;
        }
 
        if ('product.info.addtocart' == $observer->getEvent()->getBlock()->getNameInLayout()) {
 
            if (!Mage::getStoreConfig('advanced/modules_disable_output/'.self::MODULE_NAME)) {
 
                $transport = $observer->getEvent()->getTransport();
 
                $block = new Inchoo_Selectify_Block_QtyInputToSelect();
                $block->setPassingTransport($transport['html']);
                $block->toHtml();
            }
        }
 
        return $this;
    }
}

And that’s it. You will notice that I am using Mage::getStoreConfig(‘advanced/modules_disable_output/’.self::MODULE_NAME) to check if the module’s output is actually enabled/disabled. If “disabled” then extension has no effect on the frontend, meaning it does not do anything in this case.

Also notice the setPassingTransport method. It’s a custom method I wrote on the Inchoo_Selectify_Block_QtyInputToSelect block class just to abstract some code into it.

And finally take a look at the _generateQtyInputToSelectHtml method. I dumped a chunk of JavaScript code in it. This JavaScript actually handles the change of “input text” field into the “select” field with a bit of help from PrototpyJS. Which implies Prototype must be loaded on page for this to work.

Now, the above example might not be the perfect example of clean abstracted code. Especially because of the fact that I have a pure JavaScript in my PHP class. Obviously more proper place for this would be into the view file. However, I deliberately refused to go with the standard view file from theme folder as I want to keep my extension out of the theme folders.

BEFORE

AFTER

I did not took additional time to check how does this reflect to block caching or similar, but I for one fancy the approach for given (not all) frontend situations.

Please keep in mind that entire article speaks on extensions that are suppose to be super easy to distribute and remove. Leaving no negative impact on the custom themes, etc.

Feel free to share your ideas or other approaches you might have for keeping your stuff out of the theme folders.

Interested in hiring us?

Have a chat with us. You would be surprised how small changes can make your business even more successful.


18 comments

  1. Hi everyone, im a newbie in magento module development, i need to do the same without js,

  2. Can’t work out how to do this at all. Would it be alright for you to stick it as an attatchment?

  3. Hello,

    I am new to the magento CMS and I share your point of view (vis a vis magento and the need to have all the PHP file in the same dir)

    Since I am new to magento (began this morning) , I tried your solution and have few difficulties to reproduce it. COuld you send me or share here your files?

    thx a lot in advance

  4. Hi,Branko

    Thx for this post, i want to get more detail of it.I have tried this in local folder, what i have done is i have changed inchoo namespace MyCompany and module name as Change, Now i have placed config.xml in etc folder and in Block folder i have created QtyInputToSelect.php file and in Model folder i have created Observer.php. I have already created MyCompany_Change.xml in etc/modules folder.Deleted cache from folder var,But this doesn’t work for me.TIA

  5. Ok I think I get it, correct me if I am wrong, but when you are calling the toHtml() you are actually just passing back the transport object with it being modified, its not really returning your html to anything because it hasn’t really been called by anything that will output it to a template, but since you modified the transport object when you get out of the observer the transport object is still containing your modification which that html is return to a template that was probably called by a getChildHtml() method.

  6. I am a little confused after doing this on my project as to how and why it is working. For one, if you are looking to see if you are in a certain block and then you fire the observer off with your method, you are getting the transport which you then set to the text of a different block appending the current html to whatever you are doing with your method. You run the toHtml on it and this works for some reason without duplicating the content. I don’t get that because it goes through the toHtml method and returns the transport html with your appended text, then when the observer is done it still goes back to the toHtml method for the original block that you fired the event on and returns that html.

    I have it working on my project, I am just trying to understand it. I guess there isn’t a way to do this in the to_html_before one? I tried that and that doesn’t work but you don’t have the transport at that time. Does the transport even matter? I don’t when I try this method in something other then the html_after one it doesn’t work.

    Is there away to add children blocks to blocks and have it output their html as well, I tried it with no luck.

  7. Hi Branko, interesting concept. For my extensions with template components I have started to place them in
    /app/design/frontend/base/default/template/fooman/extname/
    This way I am fairly certain that my artificial “namespace” will prevent clashes while at the same time still allowing other developers to override the output in their theme if they want to.

  8. @Dan

    Pretty much depends on the task at hand. So besides the approach above, I prefer combination of block/model/controller overwrite. As long as I keep all of my code in one extension folder, out of the theme folder I am happy :) -On top of that, lets differentiate project specific “one-time” extensions that you code for one project only putting them in /app/code/local, from those you plan to distribute for mass usage. This is what I am talking about all the time. If the extension is to be for mass usage then it’s up to developer to seriously think it trough and see how he can avoid messing around theme files. Take a look at WordPress plugins. If frontend developer needs to “style” the extension output he should be able to do so by adding some CSS rules to the sites theme. I’m not sure why most of the Magento site owners think they could mess around someone others extension code, in terms of downloading it then adjusting the extension code to do its thing. Again, think of WordPress plugins. I dont have the same feeling people are “hacking” WordPress plugins as much as they might Magento extensions. Additionally extension is here to fill the purpose. If it does not fulfill it fully, then one should code it’s own and not use that one. Hope you catch my point (not that I am right, just my thoughts).

    Thanks for the input. Appreciate it.

  9. I have been trying to do this as much as possible, I have a hard time trying to figure out the best way to do this if you aren’t just adding additional functionality or something simple like changing the display of an already generated html. What if you had to modify how the core code actually generated something, by adding additional logic, maybe your system requires more logic or restrictions, but you don’t want to touch the core code or template.

  10. @Tsvetan Changing that ID, Magento wouldn’t work anyway.
    @Anders Rasmussen Whole point of this article is to describe how to avoid situation in which some other extension could mess with same file that you want to modify. JS is totally irrelevant, that could easily be html or w/e. Usage of “core_block_abstract_to_html_after” event is actually what this article is talking about.

  11. I think this is a very important topic. Many extensions require modifications in template files, and this often leads to conflicts with theme files or other extensions.

    Your approach is interesting because it removes the need for template modifications, but it is also a hack. (HTML inserted by Javascript from a block class.) It’s easy for non-developers to quickly install the extension, but it makes it harder for developers who want to modify the HTML inserted by the extension.

    Here’s my approach:

    I recently made an extension (Crius_SkipStep1) that modifies the checkout page, and requires some code to be inserted in billing.phtml. So I included a new billing.phtml file in my module. I also made a configuration option “plug and play”. When this option is enabled, the template file from my module is used. When disabled, users have to update billing.phtml in their theme manually. This way, I try to satisfy the non-coders with the “plug and play” mode, while the coders can still use their own template file by disabling the option. I think this method supports the Magento architecture better, but of course conflicts may still arise with “plug and play”.

  12. Great!

    Just noticed something little and it depends how we will decide to modify the output.

    I see one possible problem when JavaScript is used to modify something on the frontend.

    Imagine that some smart guy decided to change the id of element in .phtml that you try modify via JavaScript. In our case to change “qty” to “my-qty”. In this case we won’t handle the element we want to modify.

    Probably the chance somebody to try to change the “qty” to “my-qty” in .phtml is one in million, but think that we must have something in mind about JavaScript approach.

    Again Great Article!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.