Ecommerce Developer
 
 

Platforms & Shopping Carts

Building a Magento Theme Start to Finish, Part 12: The Search Form

 

Search is an important aspect of good site navigation. In fact, I would argue that site search is the most important means of site navigation.

Back in episode four of this series, I commented out the search bar on the theme home page, promising that we would revisit it later. Well, now is the time.

In this episode, I am going to bring the search form back, position and style it with CSS, and use JavaScript to make it appear when a user interacts with the search link.

Series Links

Download Source Files

Making the Search Form Reappear

As I mentioned above, I commented out the search form back in episode four. To make that form reappear, I need to open 2columns-right.phtml from app > design > frontend > default > pine > template > page.

The search form can be found at about line 51, and I simply need to remove the HTML comment indicator <!-- and closing tag -->.

The search form will now reappear at the top of the home page, tossing the balance of my layout into general chaos.

Screen capture shows the search form and broken page layout

Adjusting the Search Form CSS

Bringing order to chaos starts by seeking out the .search-bar class in boxes.css around line 331. As a reminder, boxes.css is located at skin > frontend > default > pine > css.

I start with four quick changes: (1) add absolute positioning; (2) add a top attribute; (3) adjust the padding; and (4) eliminate the bottom margin. The new CSS description follows:

.search-bar   { position: absolute; top: 0; padding:20px 12px 25px 12px; border-bottom:1px double #dedede; background:#efefef; line-height:1.25em; }

Screen capture shows the restored layout and the repositioned search form

These changes generally put the layout back in order. Although I do have a minor space problem to deal with.

Next, I turn my attention to the class mini-search at about line 339 in boxes.css. I want to remove the left padding and adjust the width. I am also going to change the background.

/* Mini search */
.mini-search {
    position:relative;
    z-index:97;
    float:right;
    width:300px;
    background:url(../images/ search-back-1.png) no-repeat 0 50%;
    }

I go back to the _ search-bar_ class, where I will change the background and border attributes and add rounded corners.

.search-bar   { position: absolute; top: 0; padding:20px 12px 25px 12px; background:#372016  no-repeat; border-radius: 0px 0px 20px 20px; /* CSS 3 */ -moz-border-radius: 0px 0px 20px 20px; /* Firefox */   -icab-border-radius: 0px 0px 20px 20px; /* iCab */  -khtml-border-radius: 0px 0px 20px 20px; /* Konqueror */  -webkit-border-bottom-left-radius: 20px; -webkit-border-bottom-right-radius: 20px; /* Chrome and Safari */ line-height:1.25em; }

The next style change is to adjust the width of the search text field. The CSS description we want is at about line 346 of boxes.css (remember line numbers change as we go). Here is the new code.

.mini-search .input-text { width:203px; margin-right:10px; border: none; background: transparent; } 

The "go" button that had been part of the Modern Theme—on which the Pine Theme is based—doesn't really work with the dark brown search form, so I need to navigate to form.mini.phtml in app > design > frontend > default > pine > template > catalogsearch and find the image link on about line 32. I replace the image URL so that it points at a "Search" button I created for the Pine Theme.

The updated code looks like this:

<input type="image" src="<?php echo $this->getSkinUrl('images/search-back-2.png') ?>" alt="<?php echo $this->__('Search') ?>" />

So you may see the new code in context, the code for the entire form follows:

<div class="mini-search">
<form id="search_mini_form" action="<?php echo $this->helper('catalogSearch')->getResultUrl() ?>" method="get">
    <fieldset>
        <legend><?php echo $this->__('Search Site') ?></legend>
            <input id="search" type="text" class="input-text" name="<?php echo $this->helper('catalogSearch')->getQueryParamName() ?>" value="<?php echo $this->helper('catalogSearch')->getEscapedQueryText() ?>" />
            <input type="image" src="<?php echo $this->getSkinUrl('images/search-back-2.png') ?>" alt="<?php echo $this->__('Search') ?>" />
            <div id="search_autocomplete" class="search-autocomplete"></div>
            <script type="text/javascript">
            //<![CDATA[
                var searchForm = new Varien.searchForm('search_mini_form', 'search', '<?php echo $this->helper('catalogSearch')->__('Search entire store here...') ?>');
                searchForm.initAutocomplete('<?php echo $this->helper('catalogSearch')->getSuggestUrl() ?>', 'search_autocomplete');
            //]]>
            </script>
    </fieldset>
</form>
</div>

With these changes in place, the search form is in ready and not too bad looking.

Screen capture shows the search form in place and styled

Making the Search Form Disappear

In my plans for the Pine Theme, I wanted the search form to be hidden from sight until a user hovers over the word "Search" in the top navigation. So I now need some JavaScript that will hide the search form and make it reappear when a user interacts with it.

I am going to write this JavaScript from scratch, starting with a blank file called searchslide.js. I must also add this JavaScript file to the page's <head>. To do this, I go to page.xml at app > design > frontend > default > pine > layout and add the following line of code at the end of the list of JavaScript files.

<action method="addJs"><script>lib/searchslide.js</script></action>

With the file added, I write my function. The function will change the search form's top attribute value, effectively pushing it off of the top of the visible page. I will also evoke the function using an event listener so that the search form will be hidden when the page loads.

//Hide the search form
function hideSearch(){
	
	var searchForm= document.getElementById('search-expand');
	searchForm.style.top = "-67px";
	//upSearch();

}


//Add an event listener that works in all browsers
function addEvent(el, eType, fn, uC) 
{
	if (el.addEventListener)
	{
	el.addEventListener(eType, fn, uC);
	return true;
	} else if (el.attachEvent) {
	return el.attachEvent('on' + eType, fn);
	} else {
	el['on' + eType] = fn;
	}
} 

addEvent(window,'load', hideSearch, false);

I needed to use an event listener that evoked the function after the page loaded because Magento loads JavaScript files in the document head before the page is complete.

If I did not use this method, I would be asking for the search form before it existed. What's more, as you may know, some versions of Microsoft's Internet Explorer (IE) use a non-standard document object model, so I needed a bit of code to convert the listeners for IE.

With my code written, I realized that I could have taken advantage of the Prototype JavaScript library, which is at the heart of the JavaScript files used throughout Magento. So I will refactor my event listener code, taking advantage of a Prototype short cut. Ultimately, I replaced this code:

//Add an event listener that works in all browsers
function addEvent(el, eType, fn, uC) 
{
	if (el.addEventListener)
	{
	el.addEventListener(eType, fn, uC);
	return true;
	} else if (el.attachEvent) {
	return el.attachEvent('on' + eType, fn);
	} else {
	el['on' + eType] = fn;
	}
} 

addEvent(window,'load', hideSearch, false);

The new code took just a single line thanks to Prototype.

Event.observe(window, 'load', hideSearch);

Waiting for the User to Hover

Now, I am going to create (1) a second event listener to monitor the "Search" link in the top navigation, and (2) a function to change the search form's position when a user interacts with that link.

Screen capture shows the search link

I need some way to identify or select the "Search" link in the DOM. In JavaScript it is often easiest to select elements by their id. There are lots of ways to work around this and to select page elements by name or relationship, as you may know. But I want to show you how to add an id tag anyway. So I navigate to links.phtml at app > design > frontend > default > pine > template > page > template.

At about line 36, I should find the following:

  <li <?php if($_link->getIsFirst()): ?> class="first"<?php elseif($_link->getIsLast()): ?> class="last"<?php endif; ?><?php echo $_link->getLiParams() ?>><?php echo $_link->getBeforeText() ?><a href="<?php echo $_link->getUrl() ?>" title="<?php echo $_link->getTitle() ?>" <?php echo $_link->getAParams() ?>><?php echo $_link->getLabel() ?><?php echo $_link->getAfterText() ?></a></li>

This PHP code dynamically creates the links that I have at the top of the Pine Theme. I want to modify the code so that a unique id is added to each anchor tag.

id="<?php echo $_link->getTitle() ?>-id" 

This short bit of PHP is added after the title so that the adjusted code reads like this:

 <li <?php if($_link->getIsFirst()): ?> class="first"<?php elseif($_link->getIsLast()): ?> class="last"<?php endif; ?><?php echo $_link->getLiParams() ?>><?php echo $_link->getBeforeText() ?><a href="<?php echo $_link->getUrl() ?>" title="<?php echo $_link->getTitle() ?>" id="<?php echo $_link->getTitle() ?>-id" <?php echo $_link->getAParams() ?>><?php echo $_link->getLabel() ?><?php echo $_link->getAfterText() ?></a></li>

Now, I can specify the search link using its unique identification, "Search-id." I will add the aforementioned event listener inside of the _ hideSearch()_ function. My new listener will invoke a function called downSearch().

//Hide the search form & Add Listener
function hideSearch(){
	
	var searchForm= document.getElementById('search-expand');
	searchForm.style.top = "-67px";
	//upSearch();
	
	Event.observe(document.getElementById('Search-id'), 'mouseover', downSearch);

}

The downSearch function changes the top attribute value for the search form.

function downSearch()
{
		
	var searchForm= document.getElementById('search-expand');
	var h = parseFloat(searchForm.style.top);
	
	if(h < 0)
	{
	searchForm.style.top = "0px";
	}

	
}

As is, the function will cause the search form to appear when a user hovers over the "Search" link. But the appearance is abrupt. The search form just shows up all at once. I would rather have it slide in slowly. So I am going to initiate a timer that incrementally increases the element's top attribute value.

function downSearch()
{
		
	var searchForm= document.getElementById('search-expand');
	var h = parseFloat(searchForm.style.top);
	
	if(h < 0)
	{
		searchForm.style.top = (h + .45) + "px";
		setTimeout(downSearch,5);
	}

	
}

Now the search form glides in from above when the user hovers over the "Search" link.

Image shows the search form at three points as it slides into view

Hiding the Search Form Again

After the form appears, I want to give the user ample opportunity to use it, but I also don't want it sticking around. So I write two functions that will hide the search form 5 seconds after the user stops hovering on the "Search" link. The first of these sets the timer, while the second moves the form up and out of view.

function upSearch()
{

	upSearchTime = setTimeout(upSearchMotion,5000);
	
}

function upSearchMotion()
{
	var searchForm= document.getElementById('search-expand');
	var h = parseFloat(searchForm.style.top);
	if(h > -67)
	{
	searchForm.style.top = (h - .45) + "px";
	upMotion = setTimeout(upSearchMotion,5);
	}
}

I also add an event listener that fires when the user's mouse pointer moves away from the "Search" link. This listener is placed in hideSearch().

Event.observe(document.getElementById('Search-id'), 'mouseout', upSearch);

Hold Still When the User Pays Attention

Now our search form appears on command and glides out of sight after 5 seconds. Unfortunately, it will even glide out of sight while a shopper is trying to use it. So I need another function that stops the upSearch() timer and a listener to control it.

function holdStill()
{
clearTimeout(upSearchTime);

}

Again, I add the listener inside of the hideSearch() function.

	Event.observe(document.getElementById('search-expand'), 'mouseover', holdStill);

Hiding the Search Form on a Mouseout

It may seem like I am going in circles. Hide the form. Show the form. Hide the form. Show the form. But these cycles are necessary to get the user experience I want. So I add yet another event listener.

	Event.observe(document.getElementById('search-expand'), 'mouseout', upSearch);

The Search Form is complete. In the next episode, I will add trust marks to the site, finishing the home page.

Related Articles

Category: Platforms & Shopping Carts | Tags: DOM, JavaScript, Magento

13 Comments

Rss-sm