1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
<html> <head> <title>My Experiment</title> <script type="text/javascript" language="javascript"> function updateResult() { var result=0; for(i=1;i<=3;i++){ if(document.getElementById("cb"+i).checked){ result += parseInt(document.getElementById("tf"+i).value) } } document.getElementById('result').innerHTML = result; } </script> </head> <body> <form> <p> <input type="checkbox" id="cb1" onClick="updateResult()" /> <input type="textfield" id="tf1" value="10" onkeyup="updateResult()"/> </p> <p><input type="checkbox" id="cb2" onClick="updateResult()" /> <input type="textfield" id="tf2" value="5" onkeyup="updateResult()"/> </p> <p> <input type="checkbox" id="cb3" onClick="updateResult()" /> <input type="textfield" id="tf3" value="20" onkeyup="updateResult()" /> </p> <p> Total Result: <span id="result">0</span> </p> </form> </body> </html>
Refactorings
No refactoring yet !
Elij
December 31, 2007, December 31, 2007 10:49, permalink
bit hacky...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
<html> <head> <title>My Experiment</title> <script type="text/javascript" language="javascript"> function updateResult() { var result=0; var items = document.getElementsByTagName("input"); var current = 1; for(i = 0; i < items.length; i++) { var item = items[i]; if(item.className != "cbitem") continue; if(item.checked) { result += parseInt(document.getElementById("tf"+current).value); } document.getElementById('result').innerHTML = result; current++; } } </script> </head> <body> <form> <p> <input type="checkbox" id="cb1" onClick="updateResult()" class="cbitem" /> <input type="textfield" id="tf1" value="10" onkeyup="updateResult()"/> </p> <p><input type="checkbox" id="cb2" onClick="updateResult()" class="cbitem" /> <input type="textfield" id="tf2" value="5" onkeyup="updateResult()"/> </p> <p> <input type="checkbox" id="cb3" onClick="updateResult()" class="cbitem" /> <input type="textfield" id="tf3" value="20" onkeyup="updateResult()" /> </p> <p> Total Result: <span id="result">0</span> </p> </form> </body> </html>
Casper
January 1, 2008, January 01, 2008 09:41, permalink
I like to do these kinds of things using Unobtrusive Javascript (UJS). UJS is a way to keep the code logic of your page completelty separate from the presentation (the HTML). The idea with UJS is like CSS is for page styling. CSS allows you to keep the styling information neatly separate from the HTML code. The same with UJS. It keeps the Javascript separate so it does not clutter the HTML. Your pages become easier to read, modify and maintain.
UJS is possible because you can instruct the browser to start your Javascript code once the browser has finished completley loading the page. That way you can layer your functionality onto the page very neatly after the browser has finished loading all HTML.
In addition to using UJS it is highly recommended to use some sort of Javascript library to make all those cumbersom loops and findElementById() trickery go away.
Here I'm using jQuery, but Prototype for example will work just as well.
About the loop. I wouldn't try to get rid of looping. It's easy to get your calculations out of sync if you do not loop over all the checkbox/input-field pairs in one go. Looping guarantees you don't need extra magic code to double check that you really did calculate everything correctly.
The idea with the implementation below is that it's completely dynamic. If you want to add a new set of checkbox/input-field pairs you just insert it into the page as a div with a class of "fieldpair". The code will automatically adapt and include the new field pair into the calculations.
Here we go. First the HTML, and then the jQuery code.
The HTML part
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
<!-- Load jQuery --> <script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.2.1.js"></script> <script type="text/javascript"> <!-- Insert the Javascript code from below here --> </script> <!-- Some fieldpairs. Insert as many as you like --> <div class="fieldpair"> <input type="checkbox" checked="1"/> <input type="textfield" value="10" /> </div> <div class="fieldpair"> <input type="checkbox" /> <input type="textfield" value="20" /> </div> <div class="fieldpair"> <input type="checkbox" /> <input type="textfield" value="30" /> </div> <!-- Result goes here --> <p id="result"> </p> <!-- This we just use for our debugging --> <pre id="log"> </pre>
The jQuery Javascript code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
// Nice to have for debugging purposes if you don't happen // to have Firefox + Firebug installed. function log(s) { $("#log").append(s + "\n"); } // Our main calculation function function calculate() { log("calculate()"); var total = 0; // This is the loop that selects our fieldpairs. It's nice and // neat with jQuery element selectors. // // For each fieldpair that has a child with a checked input // we get the sibling with a text input and add that value // to the total. jQuery does all the tricky element traversals // for us. $(".fieldpair").children("input:checked").siblings("input:text").each(function() { log(" Adding: " + this.value); total += parseInt(this.value); }); // Show the result $("#result").text("The total is: " + total); } // This is called when the document DOM has been fully loaded by the browser. $(document).ready(function() { // Here we go...start 'er up... log("Our output log"); log("=============="); log("ready()"); // Do the initial calculation. Some fields might be pre-checked. calculate(); // Attach event handlers for click event on checkboxes $(".fieldpair").children("input:checkbox").click(calculate); // Attach event handlers for keyup event on text fields $(".fieldpair").children("input:text").keyup(calculate); });
Jermaine
January 1, 2008, January 01, 2008 12:53, permalink
Casper, First off, Happy new year & many thanks dude, this is sweet coding... I'm not really good in javascript, but this is really interesting. I'm using prototype though, how will this work there? what certain things do I need to change in the code?
Casper
January 1, 2008, January 01, 2008 22:03, permalink
Hi. The Prototype code is similar to jQuery. Method names are a little different and Prototype uses $$() as a CSS selector and $() as an element id selector, so we have to update those. Also in Prototype you loop arrays manually (with function(element) { ... }) most of the time, so we update for that too. Here's the Prototype version. Enjoy :)
Javascript Prototype version
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
// Nice to have for debugging purposes if you don't happen // to have Firefox + Firebug installed. function log(s) { $("log").insert(s + "\n"); } // Our main calculation function function calculate() { log("calculate()"); var total = 0; // For each fieldpair that has a child with a checked input // we get the sibling with a text input and add that value // to the total. $$(".fieldpair input:checked + input:text").each(function(elem) { log(" Adding: " + elem.value); total += parseInt(elem.value); }); // Show the result $("result").update("The total is: " + total); } // This is called when the document DOM has // been fully loaded by the browser. Event.observe(window, 'load', function() { log("Our output log"); log("=============="); log("ready()"); // Do the initial calculation. Some fields might be pre-checked. calculate(); // Attach event handlers for click event on checkboxes $$(".fieldpair input:checkbox").each(function(elem) { elem.observe('click', calculate); }); // Attach event handlers for keyup event on text fields $$(".fieldpair input:text").each(function(elem) { elem.observe('keyup', calculate); }); });
Jermaine
January 1, 2008, January 01, 2008 22:25, permalink
Hi Casper, Thanks 4 your fast reply... I've tried testing this, but for some reason it doesn't work... I don't even get the Log Messages. (I'm using the latest version of prototype BTW (1.6)
Casper
January 2, 2008, January 02, 2008 00:19, permalink
There was a semicolon missing in the first edit I posted. Maybe you copied that version? I tested the code over here on Firefox with Prototype 1.6. Recopy the code and try putting an alert() statement on line 28 before the first log() call to see if the window load event is getting triggered. Then keep putting alert statements further down to see how far it gets. The missing semicolon was in the log() method, so that probably killed it before.
Oh and just to double check..remember if you were using the jQuery version - do not load jQuery at the same time you load Prototype. The are not compatible. So remove the <script> tag that loads jQuery before you try it with Prototype.
Jermaine
January 2, 2008, January 02, 2008 20:35, permalink
C,
Thanks man, works wonderfully! I still need to add alot of features. I had everything with just plain javascript with ugly "getElementById's" etc... So I'll try to port that code, prototype style. If you don't mind, you can also add me on IM. It's Jermaine2028 [at] gmail [dot] com.
Jermaine
January 9, 2008, January 09, 2008 10:38, permalink
Back again.
In the mean while I've been doing some small stuffs with my little expiriment learning UJS.
However, I'm pretty much stuck with a new feature that I want to add. What I'm trying to do is dynamically Add a new Line, and I also want to be able to Remove that line as well.
The problem is that because I'm trying to add and remove lines dynamically, the ID of the 'div' has to be unique, or else it won't know what to delete. Also, when a new line is added, the whole calculation process doesn't work because I think it isn't attached to "Event.observe"?
Any help is appreciated!
Thanks!
This is what I have:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
===================================================== #The HTML: ===================================================== <script src="prototype.js" type="text/javascript"></script> <script src="calculation.js" type="text/javascript"></script> <div id="calculation_area"> <div class="fieldpair" id="unique_line"> <input type="checkbox" checked="1"/> <input type="textfield" value="10" /> </div> <div class="fieldpair" id="unique_line"> <input type="checkbox" /> <input type="textfield" value="20" /> </div> <div class="fieldpair" id="unique_line"> <input type="checkbox" /> <input type="textfield" value="30" /> </div> </div> <p><a href="#" onclick="add_line()">Add a new line</a></p> <p id="result"> </p> ===================================================== # Calculation.js: ===================================================== // Our main calculation function function calculate() { var total = 0; // For each fieldpair that has a child with a checked input // we get the sibling with a text input and add that value // to the total. $$(".fieldpair input:checked + input:text").each(function(elem) { total += parseInt(elem.value); }); // Show the result $("result").update("The total is: " + total); } function add_line() { $("calculation_area").insert({ bottom: "<div class=\"fieldpair\" id=\"unique_line\">\ <input type=\"checkbox\"/><input type=\"textfield\" value=\"10\" />\ <a href=\"#\" onclick=\"remove_line()\">Remove</a></div>"}); } function remove_line() { $('unique_line').remove() } // This is called when the document DOM has // been fully loaded by the browser. Event.observe(window, 'load', function() { // Do the initial calculation. Some fields might be pre-checked. calculate(); // Attach event handlers for click event on checkboxes $$(".fieldpair input:checkbox").each(function(elem) { elem.observe('click', calculate); }); // Attach event handlers for keyup event on text fields $$(".fieldpair input:text").each(function(elem) { elem.observe('keyup', calculate); }); });
Casper
January 9, 2008, January 09, 2008 14:18, permalink
You have a couple of bugs. First IDs always need to be unique in your HTML code. Now you've named them all "unique_line". That's invalid HTML. You need to name them "unique_line_1/2/3/4" in case that's the way you want to do it.
Second..onclick="remove_line"..remove_line will be called with the element that triggered the event as an argument. From there you could traverse up the DOM to find your fieldpair and remove it. No need for unique ID's.
Third. If you're practicing UJS then you've been naughty and added an onclick event in the HTML for your "Add a new line" link. That's not UJS :) UJS means no Javascript among your HTML code.
Now..I fiddled a little with the code and added some fancy schmancy Scriptaculuos effects to it also to make the adding and removing of lines appear smooth and "hey that's neat" web 2.0-ish.
Google for Scriptaculous to find their package. Download it, extract it, and put all the files from the "lib" directory of the package into a directory you name "scriptaculous" that sits in the same directory where you have your "prototype.js" file. That way you can load the scriptaculous stuff in your HTML code by including scriptaculous/scriptaculous.js, as I've done in the example below.
If you don't want the scriptaculous effects just remove it from my code and remove the "Effect.*" code lines. Also note that I add the new fieldpair as "display: none", so that the scriptaculous effect can later reveal it. If you remove the effects also remember to remove that style. Otherwise the new fieldpair line will never show up.
Here's the code:
Javascript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
// Nice to have for debugging purposes if you don't happen // to have Firefox + Firebug installed. function log(s) { $("log").insert(s + "\n") } // Our main calculation function function calculate() { log("calculate()"); var total = 0; // For each fieldpair that has a child with a checked input // we get the sibling with a text input and add that value // to the total. jQuery does all the tricky element traversals // for us. $$(".fieldpair input:checked + input:text").each(function(elem) { log(" Adding: " + elem.value); total += parseInt(elem.value); }); // Show the result $("result").update("The total is: " + total); } function add_line() { // insert() inserts at bottom by default $("calculation_area").insert( '<div class="fieldpair" style="display: none">' + ' <input type="checkbox"/>' + ' <input type="textfield" value="10"/>' + ' <a href="#">Remove</a>' + '</div>' ); // Since we just inserted the element as the last // element of the calculation_area div it's easy // to grab it like this: var fieldpair = $("calculation_area").childElements().last(); // Now we can use CSS selectors to select the other // elements inside the new fieldpair div, and add // the event observers we want. fieldpair.down("input:checkbox").observe('click', calculate); fieldpair.down("input:text").observe('keyup', calculate); fieldpair.down("a").observe('click', remove_line); // Scriptaculous effect Effect.BlindDown(fieldpair, { duration: 0.4 }); } function remove_line(event) { // Prototype always calls observers with the // event as argument. From there we can grab the // element that triggered the event, and from there // we use a CSS selector to find the first parent // element (up-element) that matches the ".fieldpair" // div. And then we remove it from the DOM. Easy :) var fieldpair = event.element().up(".fieldpair"); // Scriptaculuos effect Effect.BlindUp(fieldpair, { duration: 0.4, afterFinish: function() { // Remove the element from the DOM fieldpair.remove(); // Recalculate our result since the box might // have been checked. calculate(); } }); // If you don't want the effects just delete the // Effect stuff above and uncomment the lines below: // // fieldpair.remove(); // calculate(); } // This is called when the document DOM has // been fully loaded by the browser. Event.observe(window, 'load', function() { log("Our output log"); log("=============="); log("ready()"); // Do the initial calculation. Some fields might be pre-checked. calculate(); // Attach event handlers for click event on checkboxes $$(".fieldpair input:checkbox").each(function(elem) { elem.observe('click', calculate); }); // Attach event handlers for keyup event on text fields $$(".fieldpair input:text").each(function(elem) { elem.observe('keyup', calculate); }); // Attach click handler to the add button $("add_button").observe('click', add_line); });
HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
<script type="text/javascript" src="prototype.js"></script> <script type="text/javascript" src="scriptaculous/scriptaculous.js"></script> <div id="calculation_area"> <div class="fieldpair"> <input type="checkbox" checked="1"/> <input type="textfield" value="10" /> </div> <div class="fieldpair"> <input type="checkbox" /> <input type="textfield" value="20" /> </div> <div class="fieldpair"> <input type="checkbox" /> <input type="textfield" value="30" /> </div> </div> <button id="add_button">Add a new line</button> <p id="result"> </p> <pre id="log"> </pre>
Jermaine
January 9, 2008, January 09, 2008 14:51, permalink
You have got to be freakin kidding me! This again is some sweet sugary coding. Thanks alot bro.
The scriptaculous is definitely a nice add. I was really struggling with this, Didn't know that this could be done without the unique div id's... Can't thank you enough.
If there are many good (must-read) tutorials, guides or whatever, pls. do post them.
Thanks!
- J.
Casper
January 9, 2008, January 09, 2008 20:22, permalink
Jermaine you are welcome. For Prototype it's best just to read the API docs:
http://www.prototypejs.org/api/
They are pretty good, and contain some decent examples. Usually when there's something I can't figure out how to do, and I can't find it in the API docs, I search with google "prototype javascript <the thing I want to do>". You will find some good snippets and examples that way. Just keep at it and it will get easier with a little time and experience :) Good luck.
Jermaine
January 13, 2008, January 13, 2008 11:24, permalink
Now I've been messing around a bit and found something interesting in the JS code.
It seems that when I want to put extra styling (e.g. <span class="my_custom_style"> .... </span>) in my HTML within the <div class="fieldpair">....</div> piece, the whole calculation doesn't work anymore.
Probably because the JS code is defined like this (correct me if I'm wrong):
Line 17:
$$(".fieldpair input:checked + input:text").each(function(elem) => with the "+" sign.
Here's a code example in my HTML where the calculation part stopped working....
1 2 3 4
<div class="fieldpair"> <input type="checkbox" checked="1"/> <span class="big_name">your number: </span> <input type="textfield" value="10" /> </div>
In order to make the above work, I have to do something like this in the JS code:
1 2
$$(".fieldpair input:checked + span + input:text").each(function(elem)
which ofcourse is ugly.. hopefully there is a much more cleaner alternative for this
Casper
January 13, 2008, January 13, 2008 12:16, permalink
Change the '+' to '~' and you should be good to go :)
You can read about CSS3 selectors here:
http://www.w3.org/TR/css3-selectors/
Jermaine
January 13, 2008, January 13, 2008 12:24, permalink
Thanks :)
I'm wondering though how IE6+ will react to this, hopefully it will play nice.
Jermaine
January 13, 2008, January 13, 2008 13:18, permalink
Well here I am again :)
Still one ini mini problem. Now "~" did work for me, but only if I put the styles before or after the calculated elements. However when I'm trying to put the styles *around* the Elements (that needs to be calculated)... it goes wrong.
I've read prototype's API on enumerable, and tried several of the methods. http://prototypejs.org/api/enumerable/
I've also played with several CSS3 selectors (including ?, [], * ) but with no real success :(
This is what I'm trying to do in my HTML. Got a cure for this? I think that the code in the JS file (line 17) needs to be re-factored??
$$(".fieldpair input:checked ~ input:text").each(function(elem)
1 2 3 4 5 6 7 8 9
<div class="fieldpair"> <span class="left"> <input type="checkbox" checked="1"> </span> <span class="right">Your Number: <input type="textfield" size="4" value="<%= @price_tag.price %>"> </span> </div>
Casper
January 13, 2008, January 13, 2008 14:05, permalink
Yes a construction like that gets too complicated for fancy one-liner CSS selecting.
So you need to do it "manually". I also found a bug in the selector for the text field so I updated that one.
Here's the refactored line:
1 2 3 4 5 6 7 8
$$(".fieldpair").each(function(fieldpair) { if (fieldpair.down("input:checked")) { var input = fieldpair.down('input:[type="textfield"]'); log(" Adding: " + input.value); total += parseInt(input.value); } });
Hi Everyone,
I'm having a hard time with this.
I have several checkboxes, where each checkbox belongs to a specific input field (witch has a value). So whenever the user clicks on the checkbox, each value of the associated input field is added up and placed in the 'result' ID.
The thing is, I'm doing a loop now, in this case 3 times (since the number of checkboxes & input fields is also 3). But what if I would have 10 or 20 of those input fields & checkboxes?? I would then have to change the number of loops everytime right??
Ideally I would like to have something where it somehow can do the same stuff without a loop, and where I can add 30 checkboxes & input field if I want where everything still works without changing the javascript each and everytime.
Any suggestions?
Many thanks in advance!
- J.