Quantcast
Viewing latest article 5
Browse Latest Browse All 8

HauteLook Dynamics – Plugged-in Behavior at Runtime

HauteLook Dynamics is our proprietary system which allows us to alter our live website without releasing a code change. We can quickly and easily augment or even replace existing functionality. We have used it to quickly (and temporarily) fix small bugs, to insert promotions on our site, to automatically apply coupons for specific members, and even to test site changes on a portion of our member base by implementing a minimum viable product, explained later in this post.

How Does it Work?

Right now, you should be thinking, “These people at HauteLook are amazing!!! How can they do such things without releasing code changes?” Well, if indeed that’s what you’re thinking, then I’ll be happy to answer your question. Quite simply, we hook into the preDispatch() or postDispatch() methods of the controller (we use Zend Framework) with a plugin. Each plugin is a class file which encapsulates whatever functionality we’re plugging in. On top of all of this, we have a broker class which queries the database to see which plugin classes should run on a given request. Our database table, appropriately named “dynamics”, has fields to identify module, controller, and action, so that we can directly target a specific request. It also has start and end date/time, so that we can precisely control when it starts and when it ends.

We also have a table, dynamics_values, which contains values that are passed to plugins, so we can keep most of our plugins generic, and then pass specifics to them at runtime. For example, if we have a plugin class which auto-applies a coupon to our Checkout page for any new members who signed up under a certain Invite a Friend campaign, we can use this table to pass the coupon code and the invite campaign ID to the plugin. This encourages reusability and extensibility of our plugins. We have one which simply applies a coupon, and then we extend that to apply a coupon based on certain conditions. Beautiful.

Backend Plugins

Let’s look at a couple of simple plugins.

class Halo_Dynamics_RedirectTo extends Halo_Dynamics_DynamicsAbstract
{
    public function postDispatch($controller)
    {
    	$url = ($this->getValue('url') !== null) ? $this->getValue('url') : '/';
    	$redirector = new Zend_Controller_Action_Helper_Redirector;
    	$redirector->gotoUrl($url);
    }
}

The Halo_Dynamics_RedirectTo plugin is beyond simple. It receives a value from the dynamics_values table mentioned above, and redirects the user to that URL. This plugin has been used on deprecated pages, such as pages for sweepstakes that have ended, to redirect the user to our home page.

Here’s another one:

class Halo_Dynamics_HeroImage extends Halo_Dynamics_DynamicsAbstract
{
    public function preDispatch($controller) {
        $controller->view->hero_content_section = $this->getValue('hero_content_section');
    }
}

The Halo_Dynamics_HeroImage plugin takes a value from the dynamics_values table, which is the URL of an image file, and sets that value to a property in the view. From there, the view inserts the value into the src attribute of an Image may be NSFW.
Clik here to view.
element in the HTML. This plugin lets us change images on registration and login pages.

Frontend Plugins

By far, our most commonly used plugin is this one:

class Halo_Dynamics_InsertJavascript extends Halo_Dynamics_DynamicsAbstract
{
    public function postDispatch($controller) {  
    	$content = $this->getValue('content');
    	if(empty($controller->view->dynamicJs)) $controller->view->dynamicJs = "";
    	$controller->view->dynamicJs .= $content;
    }
}

On the surface, this plugin doesn’t look like it does much, and by itself, it doesn’t. It simply injects some JavaScript into whatever page it’s plugged in to. The real magic comes from the value in the dynamics_values table.

In the holiday season of 2011, we used the Halo_Dynamics_InsertJavascript plugin to insert this into every page:

$Halo.bigbro.subscribeOnce('headerStarted', function() {
	function getReplacementText() {
		var replacementText = 'Happy Holidays';
		return replacementText;
	}

	$('#headerWelcome strong').text($('#headerWelcome strong').text().replace('Hello', getReplacementText()));
});

This simply changed the message in our website’s header from something like “Hello, Chris” to “Happy Holidays, Chris”. Simple, but it gets the job done. Here’s one that changed our logo during a promo with the Breast Cancer Research Foundation:

$Halo.bigbro.subscribeOnce('headerShow_BB', function()
{
	$('#logo').css('background-image', 'url(http://cdn.hautelook.com/dynamics/2011bcrf/hl_logo_pink_ribbon.png)').css('width', '205px').css('height', '46px');
});

Here’s a slightly more complicated one. This was used to test the impact of removing the All Events tab from our home page. Notice that the shouldDoThis() function returns true for 5% of our members. This allows us to test the feature on a subset of our member base, and then to gather data on how the feature impacted the member’s behavior.

$Halo.bigbro.subscribe('headerShow', function() {
	function shouldDoThis(member) {
		return (member.member_id % 20 == 3);
	}
	
	var member = Sandbox.getMember();

	if (member && shouldDoThis(member))
	{
		var headerTabBg = 'http://www.hautelookcdn.com/dynamics/tab_sprite_161w_web.png';
		if (member.gender == 'M')
		{
			$('#headerTabs').css('background-image', 'url(http://www.hautelookcdn.com/dynamics/tab_sprite_men_bg_web.png)');
			$('#headerTabs').css('background-image', 'url(http://www.hautelookcdn.com/dynamics/tab_sprite_men_bg_web.png)');
			headerTabBg = 'http://www.hautelookcdn.com/dynamics/tab_sprite_men_161w_web.png';
		}

		$('#header_tab_all').hide(); 

		$('#headerTabs').css('width', '966px');
		$('#moduleHeader .headerTab').css('width', '161px');
		$('#moduleHeader .headerTab').css('background-image', 'url(' + headerTabBg + ')');
		$('#body_content').css('width', '966px');

		if (member.gender == 'M')
		{
			$('#men_dropdown').css('left', '-15px');
			$('#home_dropdown').css('left', '146px');
			$('#getaways_dropdown').css('left', '307px');
			$('#kids_dropdown').css('left', '468px');
			$('#women_dropdown').css('right', '140px');
			$('#beauty_dropdown').css('right', '-21px');

		}
		else
		{
			$('#women_dropdown').css('left', '-16px');
			$('#beauty_dropdown').css('left', '145px');
			$('#home_dropdown').css('left', '306px');
			$('#kids_dropdown').css('left', '467px');
			$('#men_dropdown').css('right', '139px');
			$('#getaways_dropdown').css('right', '-20px');
		}

		$Halo.bigbro.subscribeOnce('eventGrid_Populate', function() {
			$Halo.bigbro.subscribeOnce('promoSlotter_Populate', function() {
				if (location.pathname == '/events' && (location.hash.substring(1) == 'all' || location.hash == ''))
				{			
					if (member.gender == 'M')
					{
						$('#header_tab_men').click();
					}
					else
					{
						$('#header_tab_women').click();
					}
				}	
			});
		});
	}
});

I could provide countless more examples, but I’m hoping that you’re starting to see the potential of HauteLook Dynamics.

Minimum Viable Product

One of our initiatives in Development is to move to a leaner way of doing things by developing minimum viable products (MVP). The concept of MVP is that we should only implement features or changes that actually have an impact, so when we have a new idea, we should build a simple implementation (an MVP) and then measure its impact and learn from it. HauteLook Dynamics works perfectly with MVP because we can build a new feature very quickly and turn it on with a simple database entry. And as you saw in the last example above, we can show the feature to a percentage of our members, so we can measure the impact of that feature and learn from those measurements.

The products done through this process may not always be fully featured. For example, we may not include a hover state in the first iteration of a feature because it takes more Development time to do that, and the feature without a hover state is still considered viable. But if we see positive impact from the feature, we may decide that the hover state is now worth implementing. This Build-Measure-Learn feedback loop allows us to fine-tune new features so that have an exceptional positive impact on our business.


Viewing latest article 5
Browse Latest Browse All 8

Trending Articles