Archive for the ‘Technology’ Category

Solving the IE 7, IE 9 Magento & Prototype Validation Bug

UPDATE: Read this first please…

After receiving some comments from fellow web devs [see comments between 10/26 and 11/11], I decided to integrate some of their changes. I’ve also included some enhancements via my colleague, Tom Rosario. They include a couple of updated variables (hopefully eliminating any headaches with the original ‘fieldsFilled’ infinite increment issue) and a regEx that validates the the email field, once (or if) the core validation fails.

For the rest of you…read the original post [below] to gain a little background/perspective and see if this suits your current issue. Feel free to challenge me and post fixes/improvements (We’re all fighting the same [grunt, groan, teeth-gnash] IE-quirks-battle after all ;) I will also be in the process of trying to find inconsistencies and will update accordingly if I find them.

[original post below]

Hey there fellow Magento-ers,

Have you been spending countless hours scouring the internet for a solution to this odd problem, in Magento, where you use the built-in Prototype validation to validate your form and it does not? And by ‘does not [validate]‘ I mean that in Internet Explorer 7 AND 9 (of course) it manages to ‘validate’ your empty fields, when attempting to submit an empty form, and then immediately disregards this ‘validation’ and just submits the form anyway….

Annoying, right? Can’t seem to get around it, right?

Well, have no fear! I’ve already ruined my own life, productivity and sanity in order to bring you this very specific and (I think) simple solution. I wrote (with the help of my pal Tom Rosario) a little unobtrusive Class-based function to handle it!

Just to give you some background, and to ensure I’ve been as thorough as possible, I’d like to list the things I’ve already attempted and failed at. This includes both my own solutions and those suggested by experts on the web:

  • Stopping the page from loading via the location and window object: simply doesn’t work
  • Changing the version of prototype: messes up all kinds of things in Magento, namely, checkout…so don’t do it
  • Using jQuery validation: same issue occurs
  • Using the IE 7/IE 8 compatibility meta tag: should work, but I never had success with it

Now that we’ve covered some of my futile attempts, let me tell you what I know worked; putting a good, old-fashioned ‘ onsubmit=”return false” ‘ on the form itself. This, of course, is only half the work. You then need to remove this, once the validation is satisfied and only if all the required fields are filled. In my humble opinion, this is (currently) the best solution because we’re allowing the built-in validation to still do it’s thing, with an extra layer of validation to prevent any unwanted results. The best part of this is that it doesn’t negatively affect functionality in the other browsers (FF, Chrome, Safari), who weren’t experiencing this problem in the first place. [raised eyebrow]

So speaking of that extra layer, the approach is really quite simple; we prevent the form from submitting anything if all the fields with the ‘required’ classes are empty, then remove that constraint once they’re filled/validated. Prototype validation is still able to do it’s thing, and when it’s done, and all other aspects are satisfied, we go ahead and take the ‘return false’ off the form and let it submit. Pretty simple right? I’m surprised myself that it was that easy.

Here’s the Class:

/* FormValidator Fix for IE7 & IE9 */

var ie7 = $j.browser.msie && $j.browser.version=="7.0";
var ie9 = $j.browser.msie && $j.browser.version=="9.0";
var safari =$j.browser.safari;
/*--------------------------------------------------------------------------*/

var FormValidator = function (form, opts) {
	this.form = $j(form);
	this.required = form.find('.required-entry');
	this.defaults = {
		email: form.find('.validate-email')
	};
	this.options = $j.extend({}, this.defaults, opts);
	this.setup();
};
FormValidator.prototype = {
	setup: function () {
		var self = this;

		this.form.submit(function () {
			self.onSubmit();
		});
	},
	validateEmail: function () {
		var emailCheck = this.options.email.val().match(/^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i);
		if (emailCheck != null) { return true; }
		else { return false; }
	},
	onSubmit: function () {
		var self = this, numFilled = 0;
		this.required.each(function (idx) {
			if ($j(this).val().length > 0) {
				numFilled++;
			}
		});

		var valid = this.validateEmail() && (numFilled == this.required.length);
		if (valid) {
			this.form.removeAttr('onsubmit');
			this.form.submit();
			return true;
		} else {
		/*uncomment the alert below to test your numFilled variable
		if all is going well it shouldn't increment and match the
		number of required inputs you have*/
		//alert(numFilled);

		}
	}
};

Here’s the Call (put this below your form markup):

<script type="text/javascript">// <![CDATA[
	var contactForm = new VarienForm('myform', true);
if (ie7 || ie9 || safari) {
    $j('#myForm').submit(function(e){
        if ($j('#myForm').find('.required-entry:input[value=""]').length <= 0 ) { this.submit(); }
    });
    new FormValidator( $j('#myForm') );
} else {
    // remove the 'onsubmit' and don't run the function, as all non-IE browsers don't seem to have this bug
    $j('#myForm').removeAttr('onsubmit');
}
// ]]></script>

It’s really that simple kids. Please feel free to add a comment and let me know if this didn’t work for you, or if you’d like to add something.

Technology

What do you listen to? (podcast edition)

I tend to listen to a decent number of podcasts. Usually while doing the dishes, running, or something or other in the park.  Some are book/comic related and help me keep up to date with what’s coming out and how some books were that I didn’t have time to read.  Others are tech and help me get other opinions on the new libraries or trends.  I’ve found a number of fun libraries/how-tos from listening. Check them out below, what do you listen to?

Tech

Non Tech

* I listen to every episode, others I cherry-pick

Technology

Creating a Stateless Request in Magento

Have you ever wanted to create a stateless request in Magento? Something that doesn’t touch any of Magento’s sessions?  We were having issues with some of the ajax calls on our cart and checkout pages mucking with the user’s cart and had get stateless on these calls.  The issue we were having was our checkout page was loading, then a javascript include was going out and bringing code from a 3rd party relevance engine into our dom, which was in turn calling back an ajax request to our servers.  This issue with this being that at the start of the page load, the checkout session was being set to a certain state.  This state was then being sent through the rest of the page load, and the ajax calls. Unfortunately, by the time the ajax call got back to our server, the session was different in both locations, creating a race condition.  The ajax request usually won, removing the work the full page load had done with trying to process checkout.  The good news was there was nothing in the ajax call that needed to touch the session, it was just some data lookup. So, nix the session part of that call, and our troubles should be over… Magento’s api controller is the only place that implements a stateless request this but its fairly easy to do (after a bit of digging).

As long as Mage_Core_Controller_Varien_Action is a parent in your controller’s hierchy, you are good to go (it probably is).  This class has a const FLAG_NO_START_SESSION which looks promising. Digging into the code a little we see that it controls whether cookies are processed or the session is started:

<?php
...
        if (!$this->getFlag('', self::FLAG_NO_START_SESSION)) {
            $checkCookie = in_array($this->getRequest()->getActionName(), $this->_cookieCheckActions);
            $checkCookie = $checkCookie && !$this->getRequest()->getParam('nocookie', false);
            $cookies = Mage::getSingleton('core/cookie')->get();
            if ($checkCookie && empty($cookies)) {
                $this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, true);
            }
            Mage::getSingleton('core/session', array('name' => $this->_sessionNamespace))->start();
        }
view raw gistfile1.aw This Gist brought to you by GitHub.

By adding to the preDispatch() method of our Action or Controller we can toggle this:

<?php
class Ai_AjaxCatalog_Controller_Action extends Mage_Core_Controller_Front_Action
{
public function preDispatch()
{
$this->setFlag('', self::FLAG_NO_START_SESSION, 1); // Do not start standard session
parent::preDispatch();
return $this;
}
}
view raw gistfile1.aw This Gist brought to you by GitHub.

Now, any action in this controller will be stateless and not effect any sessions.

Technology

The Evolving Web: Multi-Screen Patterns

At 7:15 the alarm on my iPhone goes off, notifying me that it’s time to wake up. I briefly check my email, waiting a bit for my mind to snap out of its drowsiness, and then pull myself out of bed. Today the TV in my bedroom stays off, but I flip on the set in the living room to catch the headlines and weather while I make some coffee. Next, I sit down at my computer to fire off a quick email I forgot to send out the night before, sync up the news articles on my Kindle for the commute and head out the door to work. I see seven different screens in my first hour; three more are waiting for me at my desk at work. This is my typical device-filled morning.

Our current technological environment plays host to thousands of digital devices. People move from one screen to another, increasingly expecting their gadgets to integrate into a consistent experience across all platforms. Because of this it’s becoming more important that businesses, app creators, information architects, designers, and code slingers take responsibility for providing their clients with strategies for a multi-screen playing field– thinking beyond the smartphone to other devices. But solely creating several applications and scenarios for each device won’t cut for much longer. It’s time for us to start thinking about the relationships between different devices and how people utilize and interact with each of them.

I recently had the opportunity to attend the BrandPerfect Tour NYC. Design consultancy Precious hosted a workshop exploring several multi-screen patterns, the context of the user, and connections between devices. Their documentation of these relationships gives us a clear picture of current possibilities between devices and provide some great solutions on your next multi-platform project.

Gadgets

EBay Acquires Magento

Ebay today announced they are acquiring everyone’s favorite ecom platform Magento.  They already owned 49% of the platform and have announced plans to roll Magento into some new X.Commerce initiative.  This is a very smart move for EBay.  Have recently acquired GSI to offer an enterprise solution at the very top of the online retail food chain, EBay can even more effectively compete at the entry- and mid-levels of ecommerce.

We’re huge fans of Magento here at Ai, and will be watching this development closely.  On the one hand, the additional engineering resources, marketing, product stewardship, and enterprise support will be welcomed by ecom brands and developers alike.  Graduating from a 49% strategic investment to a fully-blown integrated product suite should come with the commensurate level of attention from EBay execs.

On the other hand, all too often we’ve seen thriving software platforms gobbled up by larger companies primarily with the intent of folding the acquired company’s customers into the acquirer’s existing product suite.  This may not be welcome news for us Magento devs out there that enjoy direct access to the source code of the product and significant engineering accumen and performance tuning experience on the platform.

We will certainly keep our eyes on this and report back to our friends and clients any important implications.  In the meantime, congratulations to the folks at Magento and founder Roy Rubin.

Ecommerce

HTML High-5

Everyone is jumping on the HTML5 bandwagon, and we at Ai are no exception! For a while I’ve been a little reluctant to introduce the new revision for our projects mainly due to the lack of solutions that would target cross-browser compatibility out of the box. As an ecommerce shop, browser compatibility means more users, means more revenue, means happy client.

HTML5 Boilerplate - A rock-solid default template for HTML5 awesome

For the past couple years I’ve been monitoring the development of HTML5 Boilerplate, a project created by Paul Irish and Divya Manian, aimed to seamlessly integrate HTML5 and modern CSS3 features into your site across all browsers (even back to IE6). The current 1.0 build was recently released with a bunch of goodies such as HTML and JS minification, image compression, cache management, updated CSS reset, custom build script, the option to choose modernizr or html5shiv, and so much more.

To learn more about the project visit html5boilerplate.com.

Technology

A Simple Java REST Client with Apache Commons HttpClient

I recently needed to build a RESTful web service client for my Java application. I wanted something simple and I had a particular set of requirements I wanted to code around (like being able to return the original request parameters along with the response) so the leaner the the actual client the better. After a little research I decided to go with a solution built on Apache Commons HttpClient which provides a rich set of abstractions for client side http interactions. I like HttpClient because it provides a higher and more functional level of abstraction than underlying java.net classes but at the same time allows you to code around whatever Http idioms you want (simple request/response, conversational services, data submission etc.) without alot of bridge code like annotations or library specific interface implementations.

Since I needed to both asynchronously POST data to a restful endpoint as well as perform GET request against another endpoint,  I abstracted all of the parameter bundling and message sending into a single processRequest() method instead of implementing separate post/get code. I also standardized the return type to a Map containing the objects I needed for my app e.g. the request parameters, the actual response and the HTTP response code (REQUEST_PARAMS, RESPONSE_BODY and RESPONSE_STATUS respectively):

processRequest():

public Map<String, Object> processRequest(String serviceUrl,
                        String sendMethod, Map<String, String> params)
{
                if (StringUtils.isBlank(serviceUrl)) {
                    throw new AiServiceException("Service URL is required");
                }
                // Essentially return a new HttpClient(), but can be pulled from Spring context
                HttpClient httpclient = getHttpClient();
                HttpMethod method = getHttpMethodFromString(sendMethod);//See Details Below
                method.setPath(serviceUrl);
                httpclient.getParams().setParameter("http.protocol.version",HttpVersion.HTTP_1_1);
                httpclient.getParams().setParameter("http.socket.timeout", Integer.valueOf(responseTimeOut));
                httpclient.getParams().setParameter("http.protocol.content-charset",charSet);
                setRequestParams(method, params);
                HashMap<String, Object> responseObject = new HashMap<String, Object>();
                try {
                        int responseCode = httpclient.executeMethod(method);
                        String respBody=getResponseBody(method);// See details Below
                        responseObject.put(REQUEST_PARAMS, params);
                        responseObject.put(RESPONSE_BODY, respBody);
                        responseObject.put(RESPONSE_STATUS, responseCode);
                        return responseObject;
                } catch (Exception e) {
                        logger.error("Error Sending REST Request [URL:"+serviceUrl+",METHOD:"+sendMethod+",PARAMS:"+params+"]", e);
                        throw new AiServiceException(e);
                } finally {
                        method.releaseConnection();
                }
        }

To make this work I  wrote a method parse an actual HttpMethod Object from a given string:
getHttpMethodFromString():

private HttpMethod getHttpMethodFromString(String methodString) {
		if (StringUtils.isNotBlank(methodString)) {
			org.springframework.http.HttpMethod parsedMethod = org.springframework.http.HttpMethod
					.valueOf(methodString.toUpperCase());
			switch (parsedMethod) {//Add other methods as needed PUT, DELETE etc.
			case GET:
				return new GetMethod();
			case POST:
				return new PostMethod();
			default:
				return new GetMethod();
			}
		}
		return new GetMethod();
	}

Finally I needed to make sure to read the entire response before returning it to caller:
getResponseBody():

protected String getResponseBody(HttpMethod method){
		//Ensure we have read entire response body by reading from buffered stream
		if(method!=null&& method.hasBeenUsed()){
			BufferedReader in=null;
			StringWriter stringOut= new StringWriter();
			BufferedWriter dumpOut = new BufferedWriter(stringOut,8192);
			try {
				 in=new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
				String line = "";
				while ((line = in.readLine()) != null) {
					dumpOut.write(line);
					dumpOut.newLine();
				}
			} catch (IOException e) {
				logger.error("Error Reading Response Stream",e);
				throw new AiServiceException(e);
			}finally{
				try {
					dumpOut.flush();
					dumpOut.close();
					if(in!=null)
						in.close();
				} catch (IOException e) {
					logger.warn("Error Closing Response Stream",e);
				}
			}
			return StringEscapeUtils.unescapeHtml(stringOut.toString());
		}
		return null;
	}

The newer Apache HttpComponents Project offers some evolutionary features over HttpClient like Plug-able authentication mechanisms, but the code shown above based on the legacy HttpClient libs works very well for my needs and is easily upgradeable should the need arise.

Technology

Jira Tabs: Open all those Jira’s at once!

Ever want to open all the Jira’s on the screen (search and filter views) in new tabs? Jess does, I do, and you should too!

For firefox and chrome we now have the JiraTabs bookmark button.

Drag this link up to your bookmarks bar: JiraTabs. Then, whenever you are on a filter or search view of Jira’s, click the button and all the jira’s on your screen will open up in new tabs

Demo:

Any updates will be made here.

Technology

Extending a Magento Controller

We’re ajaxing part of the Magento shopping cart so we need to modify/extend some of the cart controller functionality. Sometimes when modifying controller’s you have to worry about updating the routes. For this, we don’t need to, we still want all the urls to be used the same way.

app/code/local/Ai/Checkout/etc/config.xml:

<config>
    <modules>
        <Ai_Checkout>
             <version>0.0.1</version>
        </Ai_Checkout>
    </modules>
...
    <frontend>
        <routers>
            <checkout>
                <use>standard</use>
                <args>
                    <module>Ai_Checkout</module>
                    <frontName>checkout</frontName>
                </args>
            </checkout>
        </routers>
    </frontend>
</config>

app/code/local/Ai/Checkout/controllers/CartController.php:

require_once Mage::getModuleDir('controllers', 'Mage_Checkout') . DS . 'CartController.php';

class Ai_Checkout_CartController extends Mage_Checkout_CartController
{
   public function updatePostAction()
    {
    	Mage::log("NEW CONTROLLER", null, 'tim.log');
        try {
Technology

Moving Gmail Gadgets to the Right Side

I started using Remember the Milk recently but didn’t want the gmail gadget to be so far down on the left hand side of my screen. There is no built in way to move gadgets to the right hand side with the exception of chat (labels used to do this but was removed in favor of drag in drop back in late 2009).

If you don’t have anything in the right hand column, enable Right-Side Chat from Gmail Labs. We are going to add in some custom css to gmail so install either Stylist for Chrome or Stylish for Firefox.

Add the following style:

div.TZ:nth-child(8) {
    position:absolute !important;
    right:0px;
    top:165px;
    width:164px;
}

In chrome you can also restrict the domain to mail.google.com. For me, the Remember the Milk gadget was the 8th child. Play with this until it looks right for you. You may also have to play with the “top” element depending on how much room your chat gadget takes up

Technology