Main Page Content
Tracking User Choices With Javascript
I'm a JavaScript newbie.
Sure, I've read the tutorials at webmonkey, and followed along through thread after thread on the mailing lists about 'how can I get my function to insert task here. But I never really got it. Up until last week, the most complex thing I'd ever done with JS was copying other people's scripts and adapting them ever so slightly to work on my page. I was beginning to become very discouraged, but ran into a problem that was just begging to be solved with JavaScript.
I live on Maui, and work as a freelancer running my own very small web-design studio. Most of my work is for local small businesses who are usually under-represented on the web and who typically lack both the resources and understanding to make full use of the internet in their business. My biggest challenge as a designer is making enough money to survive, working on projects with a shoestring budget. My other biggest challenge is pleasing clients, who feel like they are paying WAY TOO MUCH. From talking to other designers and having worked in a local web design company for a year, I know that my rates are incredibly reasonable and that this challenge will always be there when I work with local clients.
Note: I am extremely open to working as a freelance designer/coder for other companies either here or on the mainland :)
Anyway, this amounts to fancy server side solutions (which I would just LOVE
to get into) being generally beyond the reach of many of the projects I am involved with. I have a hard enough time selling clients on the fact that they need a new website, much less needing to spend $20/month for PHP or ASP hosting. So, I usually have to find a way to solve problems that is probably not the best solution, but one that serves the purpose and does so cheaply and quickly and requiring a minimum investment. Bummer, but don't feel to sorry for me...I have it pretty good. :)The Problem I needed to solve...
Over the course of the past few months, I've been working on a site for a windsurfing company...Simmer Hawaii. This has been a great job, because I somehow managed to talk the client into letting me do whatever I want with their site. When I proposed that the best way for them to be successful on the web is to continually add to the content of the site: they believed me. That means I get to work on this on an ongoing basis, earning pretty good $$ and getting fat discounts on new wave sails and beachwear every now and then.
One of the ideas we had was to add a travel section to the website. Part of
Simmer's business is renting windsurfing gear to vacationing windsurfers. If you come to Maui to sail, you want to stay on the north shore, because that's where all the sailing is. There aren't any resorts, there aren't any hotels, there aren't any condos. On the north shore, you stay in private homes that are maintained by their owners as vacation rentals. So, this accommodations reality necessitated featuring many different vacation rental properties on the website. We also figured there would be a great deal of value in adding more keyword rich pages of content to the site.The structure called for a hierarchy of pages, 'site >> travel section >> neighborhood
>> property/unit'. That's how we'd done it in the rest of the site, so we continued to structure information that way. My wife's company, which specializes in vacation accommodations for windsurfers on Maui's north shore agreed to provide content and handle the inquiries generated. I have to mention this, because its another thing about the web that I'ma huge advocate of: using the internet to bring unique businesses together to better serve the customer public. In this arrangement, Simmer Hawaii gets a) more content for their website, b) another service that they can offer their customers, c) a small commission on each sale of a vacation. My wife's company gets a) exposure for her product/company in another venue, b) more inquiries and an expanded customer base, c) a small commission on each gear rental sale made, d) the chance to show her vendors, 'look, I'm marketing you here too'. It's a win/win situation and a model that I will continue to explore for my customers. But back to the problem...The idea behind presenting many options for vacation accommodations in the
travel section was to give visitors to the site as many choices as we can. Each neighborhood features a number of properties representing a range of price, size and level of luxury. Trouble is, visitors to the site are likely to want to inquire about more than one property. I mean, sure that one-bedroom on the beach at Sprecks might be good...but the studio in Paia is the same price and closer to town. So, the problem was to provide a mechanism by which users could add different properties to the list of those that they would inquire about, and only make them fill out one form to inquire about them all. The limitations were that the form gets submitted by CGI-email, because that's the only script available through the cheap-ass web hostThe solution I came up with...
So, how to get those choices about different properties into the CGI-Email
form...I thought about listing each property/unit on the form with a checkbox beside it and a link back to that property/unit so users could go back for reference. That seemed a little cumbersome and less than elegant. What I really wanted was a sort of 'shopping cart', to allow users to choose their properties as they browsed. So, I figured if I could 1) put checkboxes next to each property choice, 2) get the checkboxes to store a value, 3) retrieve the stored values at the form, 4) use the stored values to print the user's choices into the form as display text and form input...I'd be looking good. So, how could I do it?Because I'm a newbie, this seemed like a really complex thing to do. But it
turned out to be a really perfect 'first software development project'. The first step is above...define each step of what your script has to do. How can you write a function or anything until you know what you have to do at each step? Since my first step called for checkboxes beside each property, I started by building the property pages for each property/unit. I figured whatever I came up with function-wise for step two would be the same for each page, and just a matter of copy & paste.Step two required a way to store the values for the checked forms. Since most
people are terrified of cookies (and since writing 22 or so values into a cookie seemed really daunting) I wanted to avoid them. Sort of overkill anyway, since the state only needs to be remembered until they fill out the form. So, I chose to split the travel section into frames...one frame for displaying the content, one for storing the values of the checkboxes. My frameset code (index.html in the /travel directory) looks like this:<frameset rows="0,*" cols="*" framespacing="0" frameborder="0"> <frame src="frm_hidden_var_storage.html" name="topFrame" id="topFrame" frameborder="0" scrolling="No" noresize marginwidth="0" marginheight="0"> <frame src="travel.html" name="mainFrame" id="mainframe" frameborder="0" scrolling="Auto" noresize marginwidth="0" marginheight="0"> </frameset> <noframes> <body bgcolor="#FFFFFF" text="#000000" topmargin="0" marginheight="0"> Sorry, you can't see frames...this site wasn't built for you. </body> </noframes>
Note: The framset puts the page where the values are stored on top. This
is imperative. Otherwise, the page in the bottom of the frameset is trying to access objects that don't exist yet. You'll see what I'm talking about when I get into the functions that make it work...All that's on the page 'frm_hidden_var_storage.html' is a form with 22 <input
type="hidden" name="[name that corresponds with a property/unit]" value="false">. When a user checks a checkbox, a function changes the value of that property/unit's <input type="hidden"... to true. Like this:function updateValue(propName) { var hideyElement = eval('parent.topFrame.document.form1.' + propName.name); hideyElement.value = propName.checked; }
This function is called by the onClick event handler on the checkbox...onClick="updateValue(this);".
In the first line, I make a variable called hideyElement. Now, I had help with this part from Erik Mattheis. I don't really understand why I have to eval the string 'parent.topFrame.document.form1.' before concatenating the argument.name to it, but it didn't' work when I did it the other way...var hideyElement = parent.topFrame.document.form1.propName.name;...I got an error telling me that ' parent.topFrame.document.form1.propName' is null or not an object. The conceptual difference between what JS thinks of as an object, and what it thinks of as a string is more than I can currently grasp. But thanks to thelist, I was able to get beyond this point in spite of my limited understanding. The argument passed to the function is a reference to the checkbox itself. It is important to note that each checkbox in the site has a name attribute that is exactly the same as the name attribute for it's corresponding <input type="hidden"... in the hidden frame. That way, my function can just plug the name value from the checkbox into the variable that references the hidden input on the other page and change that objects value attribute to the checked value of the checkbox. Following? Since 'true' is the value of the checkbox when its checked, I can change the value of the hidden input to the checked value of the checkbox, and it evaluates to changing the hidden inputs' value to 'true'.That was easy enough...now to get those values printed on the page in a useful
format...One of the very first lessons in my JavaScript tutorials in Danny Goodman's
'JavaScript Bible' (a resource I highly recommend to any newbie wanting to get their feet wet in JS) deals with looping through an array. Since that intuitively seemed like the way to get all the values from my hidden inputs, and then do something based on those values, I started by making an array to loop through:var AllProperties = new Array(22); AllProperties[0] = parent.topFrame.document.form1.tavares_bay_home; AllProperties[1] = parent.topFrame.document.form1.tropical_hideaway; AllProperties[2] = parent.topFrame.document.form1.rays_hanawi; AllProperties[3] = parent.topFrame.document.form1.rays_mauna; AllProperties[4] = parent.topFrame.document.form1.rays_lua_pele; AllProperties[5] = parent.topFrame.document.form1.paia_studio; AllProperties[6] = parent.topFrame.document.form1.kehau; AllProperties[7] = parent.topFrame.document.form1.kai_hale; AllProperties[8] = parent.topFrame.document.form1.hookipa_bayview; AllProperties[9] = parent.topFrame.document.form1.hale_lea_lea1; AllProperties[10] = parent.topFrame.document.form1.hale_lea_lea2; AllProperties[11] = parent.topFrame.document.form1.hale_lea_lea3; AllProperties[12] = parent.topFrame.document.form1.hale_kulani1; AllProperties[13] = parent.topFrame.document.form1.hale_kulani2; AllProperties[14] = parent.topFrame.document.form1.hale_kulani3; AllProperties[15] = parent.topFrame.document.form1.hale_kulani4; AllProperties[16] = parent.topFrame.document.form1.hale_kulani5; AllProperties[17] = parent.topFrame.document.form1.aqua_terra1; AllProperties[18] = parent.topFrame.document.form1.aqua_terra2; AllProperties[19] = parent.topFrame.document.form1.aqua_terra3; AllProperties[20] = parent.topFrame.document.form1.aina_lani1; AllProperties[21] = parent.topFrame.document.form1.aina_lani2;
This array is just a list of all of the <input type="hidden"...
objects on the other, hidden page in the frameset. These objects are the objects whose values were changed from their default (false) to true in the checkbox updateValue(propName) function. So now that I have them in an array, I can loop through that array and find all of the values that are true, like this:for (var i = 0; i < AllProperties.length ; i++){ if (AllProperties[i].value == "true"){...
This is one of the most basic constructs in any programming language, what
it does is to make a variable (i), set it to 0. Then it compares the variable to some other statement, in this case, the length of the AllProperties array (AllProperties.length), and increments the variable 'i' by one. So, the loop will run as many times as i < the length of the AllProperties array. And the value of 'i' always corresponds with an index number of a 22 item array. Every time it runs, it does something:var linkStr = "<a href=\"" + PropLinks[i] + ".html\" target=\"_self\">" document.write(linkStr + PageProperties[i] + "</a><br>") }
It makes a variable that contains a string ( "<a href=\""),
since I wanted my selected properties to display on the page not merely as text, but as links back to that properties page in the site. That calls for another array item, PropLinks[i]. PropLinks[i] is another array I made to serve as a sort of lookup table for the first array. Its like the AllProperties array, except that instead of holding a list of objects, it holds a list of strings that when inserted into this function are included in the output string. So basically, <a href=\"" + PropLinks[i] + ".html\" target=\"_self\"> comes out as "<a href=" + "prop_apagesname" + ".html target="self">". That way, I can write a link back to the associated, chosen property as I write that chosen property into the page for display, which happens in the 2nd line. That takes carefully canceling out the right quotation marks, so my attributes in the tag are properly quoted. The variable linkStr (which is constructed by pulling items out of an array of link values) builds the opening <a> tag. Another array's value (with the same index, 'i' as the other arrays) gets written in next, then the link tag is closed and a line break inserted by adding"</a><br>".This gets done for each 'true' value in the hidden inputs on the hidden frame
page. Cool, so that's how I get the list of links back to the selected properties.But I also needed to get those values into my CGI-email form...
So, I made another array that contained the values that I want to pass to CGIemails
text template. It's just another array of strings called PageProperties. It's values get inserted into the page like this:<script> <!-- for (var i = 0; i < AllProperties.length ; i++){ if (AllProperties[i].value == "true"){ var inputStr1 = "<input type=\"hidden\" value=\"" var inputStr2 = "\" name=\"" document.write(inputStr1 + PageProperties[i] + inputStr2 + AllProperties[i].name + "\">") } } //--> </script>
This is really no different than the other output loop, except that it creates
hidden input fields in the form to pass the dynamically generated values to CGI-Email's text template. Each time through the loop, the function creates a ' <input type="hidden" name="AllProperties[i]Name" value="PageProperties[i]">. Then, in the text template for cgi-email, I have a 'accommodations preferences:' field that has little [] slots for each of the AllProperties array items...pretty simple.Yes...it does leave a lot to be desired...
So, it's not foolproof. I had to put a function to bust the frames on all of
the pages in the site that are not in the /travel section, otherwise, if you followed a link from the travel section to another area of the site and then went back, you'd be in a double frame. Since I did that, if you check some properties while you're browsing, then go look at the sail line or women's swimsuits and come back to the travel section...your checked values are gone. And I still have to write a function that checks the boxes if you check it once, leave the page, and then go back. And if you have JS turned of, it doesn't work. But like I said at the beginning of the article, it doesn't have to be a foolproof solution, just good enough to solve the problem with a minimum investment of resources. The client is happy because the inquiries we get now have information about which properties the inquirer was interested in. The user is happy because they only have to fill out one form, and can go back to the properties they checked while filling out the form. I'm happy because I learned more about planning and building web sites/web applications.Thanks!